diff options
Diffstat (limited to 'XMPFiles/source')
86 files changed, 7769 insertions, 1783 deletions
diff --git a/XMPFiles/source/FileHandlers/AIFF_Handler.cpp b/XMPFiles/source/FileHandlers/AIFF_Handler.cpp index bd59ea7..8c9d604 100644 --- a/XMPFiles/source/FileHandlers/AIFF_Handler.cpp +++ b/XMPFiles/source/FileHandlers/AIFF_Handler.cpp @@ -135,7 +135,7 @@ AIFF_MetaHandler::AIFF_MetaHandler ( XMPFiles * _parent ) : mChunkBehavior(NULL), mChunkController(NULL), mAiffMeta(), mXMPChunk(NULL), mNameChunk(NULL), mAuthChunk(NULL), - mCprChunk(NULL), mAnnoChunk(NULL), mFileType(0) + mCprChunk(NULL), mAnnoChunk(NULL) { this->parent = _parent; this->handlerFlags = kAIFF_HandlerFlags; @@ -368,9 +368,23 @@ void AIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) updateLegacyChunk( &mCprChunk, kChunk_CPR, AIFFMetadata::kCopyright ); updateLegacyChunk( &mAnnoChunk, kChunk_ANNO, AIFFMetadata::kAnnotation ); } - + + XMP_ProgressTracker* progressTracker=this->parent->progressTracker; + // local progess tracking required because for Handlers incapable of + // kXMPFiles_CanRewrite XMPFiles call this Update method after making + // a copy of the orignal file + bool localProgressTracking=false; + if ( progressTracker != 0 ) + { + if ( ! progressTracker->WorkInProgress() ) + { + localProgressTracking = true; + progressTracker->BeginWork (); + } + } //write tree back to file - mChunkController->writeFile( this->parent->ioRef ); + mChunkController->writeFile( this->parent->ioRef ,progressTracker); + if ( localProgressTracking && progressTracker != 0 ) progressTracker->WorkComplete(); this->needsUpdate = false; // Make sure this is only called once. } // AIFF_MetaHandler::UpdateFile diff --git a/XMPFiles/source/FileHandlers/AIFF_Handler.hpp b/XMPFiles/source/FileHandlers/AIFF_Handler.hpp index 13bfa02..1ccb6a9 100644 --- a/XMPFiles/source/FileHandlers/AIFF_Handler.hpp +++ b/XMPFiles/source/FileHandlers/AIFF_Handler.hpp @@ -49,7 +49,8 @@ static const XMP_OptionBits kAIFF_HandlerFlags = (kXMPFiles_CanInjectXMP | kXMPFiles_CanExpand | kXMPFiles_PrefersInPlace | kXMPFiles_CanReconcile | - kXMPFiles_AllowsSafeUpdate + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress ); /** @@ -98,7 +99,7 @@ private: AIFF_MetaHandler (): mChunkController(NULL), mChunkBehavior(NULL), mAiffMeta(), mXMPChunk(NULL), mNameChunk(NULL), mAuthChunk(NULL), - mCprChunk(NULL), mAnnoChunk(NULL), mFileType(0) {}; + mCprChunk(NULL), mAnnoChunk(NULL) {}; // ----- MEMBERS ----- // @@ -119,7 +120,7 @@ private: IChunkData *mAnnoChunk; /** Type of the file, either AIFF or AIFC */ - XMP_Uns32 mFileType; + //XMP_Uns32 mFileType; // ----- CONSTANTS ----- // diff --git a/XMPFiles/source/FileHandlers/ASF_Handler.cpp b/XMPFiles/source/FileHandlers/ASF_Handler.cpp index f7026bb..14bf225 100644 --- a/XMPFiles/source/FileHandlers/ASF_Handler.cpp +++ b/XMPFiles/source/FileHandlers/ASF_Handler.cpp @@ -49,14 +49,11 @@ bool ASF_CheckFormat ( XMP_FileFormat format, IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent); XMP_Assert ( format == kXMP_WMAVFile ); - IOBuffer ioBuf; - - fileRef->Rewind(); - if ( ! CheckFileSpace ( fileRef, &ioBuf, guidLen ) ) return false; - + if ( fileRef->Length() < guidLen ) return false; GUID guid; - memcpy ( &guid, ioBuf.ptr, guidLen ); + fileRef->Rewind(); + fileRef->Read ( &guid, guidLen ); if ( ! IsEqualGUID ( ASF_Header_Object, guid ) ) return false; return true; @@ -96,7 +93,7 @@ void ASF_MetaHandler::CacheFileData() XMP_IO* fileRef ( this->parent->ioRef ); if ( fileRef == 0 ) return; - ASF_Support support ( &this->legacyManager ); + ASF_Support support ( &this->legacyManager,0 ); ASF_Support::ObjectState objectState; long numTags = support.OpenASF ( fileRef, objectState ); if ( numTags == 0 ) return; @@ -174,7 +171,7 @@ void ASF_MetaHandler::UpdateFile ( bool doSafeUpdate ) XMP_IO* fileRef ( this->parent->ioRef ); if ( fileRef == 0 ) return; - ASF_Support support; + ASF_Support support(0,this->parent->progressTracker); ASF_Support::ObjectState objectState; long numTags = support.OpenASF ( fileRef, objectState ); if ( numTags == 0 ) return; @@ -233,7 +230,10 @@ void ASF_MetaHandler::UpdateFile ( bool doSafeUpdate ) updated = SafeWriteFile(); } else { - + + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)packetLen ); + // current XMP chunk size is sufficient -> write (in place update) updated = ASF_Support::WriteBuffer(fileRef, objectState.xmpPos, packetLen, packetStr ); @@ -257,6 +257,7 @@ void ASF_MetaHandler::UpdateFile ( bool doSafeUpdate ) } + if ( progressTracker != 0 ) progressTracker->WorkComplete(); } } @@ -276,7 +277,7 @@ void ASF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) bool ok; XMP_IO* originalRef = this->parent->ioRef; - ASF_Support support; + ASF_Support support(0,this->parent->progressTracker); ASF_Support::ObjectState objectState; long numTags = support.OpenASF ( originalRef, objectState ); if ( numTags == 0 ) return; @@ -285,7 +286,21 @@ void ASF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) ASF_Support::ObjectIterator curPos = objectState.objects.begin(); ASF_Support::ObjectIterator endPos = objectState.objects.end(); - + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) { + float nonheadersize = (float)(xmpPacket.size()+kASF_ObjectBaseLen+8); + bool legacyChange=this->legacyManager.hasLegacyChanged( ); + for ( ; curPos != endPos; ++curPos ) { + if (curPos->xmp) continue; + //header objects are taken care of in ASF_Support::WriteHeaderObject + if ( ! ( IsEqualGUID ( ASF_Header_Object, curPos->guid) && legacyChange ) ) { + nonheadersize+=(curPos->len); + } + } + curPos = objectState.objects.begin(); + endPos = objectState.objects.end(); + progressTracker->BeginWork ( nonheadersize ); + } for ( ; curPos != endPos; ++curPos ) { ASF_Support::ObjectData object = *curPos; @@ -316,6 +331,7 @@ void ASF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) ok = support.UpdateFileSize ( tempRef ); if ( ! ok ) XMP_Throw ( "Failure updating ASF file size", kXMPErr_InternalFailure ); + if ( progressTracker != 0 ) progressTracker->WorkComplete(); } // ASF_MetaHandler::WriteTempFile diff --git a/XMPFiles/source/FileHandlers/ASF_Handler.hpp b/XMPFiles/source/FileHandlers/ASF_Handler.hpp index 2a4f63e..f8b883e 100644 --- a/XMPFiles/source/FileHandlers/ASF_Handler.hpp +++ b/XMPFiles/source/FileHandlers/ASF_Handler.hpp @@ -36,7 +36,8 @@ static const XMP_OptionBits kASF_HandlerFlags = ( kXMPFiles_CanInjectXMP | kXMPFiles_CanReconcile | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | - kXMPFiles_NeedsReadOnlyPacket ); + kXMPFiles_NeedsReadOnlyPacket | + kXMPFiles_CanNotifyProgress ); class ASF_MetaHandler : public XMPFileHandler { diff --git a/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp b/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp index 5b22a13..a58594f 100644 --- a/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp +++ b/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp @@ -15,8 +15,10 @@ #include "XMPFiles/source/XMPFiles_Impl.hpp" #include "source/XMPFiles_IO.hpp" #include "source/XIO.hpp" +#include "source/IOUtils.hpp" #include "XMPFiles/source/FileHandlers/AVCHD_Handler.hpp" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" #include "source/UnicodeConversions.hpp" #include "third-party/zuid/interfaces/MD5.h" @@ -405,6 +407,16 @@ static bool MakeLeafPath ( std::string * path, XMP_StringPtr root, XMP_StringPtr *path += ".MPL"; if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + } else if ( XMP_LitMatch ( suffix, ".m2ts" ) ) { // Special case of ".mts" for the stream file. + + path->erase ( partialLen ); + *path += ".mts"; + if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + + path->erase ( partialLen ); + *path += ".MTS"; + if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + } // Still not found, revert to the original suffix. @@ -1221,10 +1233,15 @@ static bool ReadAVCHDPlaylistExtensionData ( XMPFiles_IO & mplFile, if ( extensionData.mMakersPrivateDataStartAddress > 0 ) { - if ( ! avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPanasonicPrivateData.mPresent ) return false; + // return true here because all the data is already read successfully except the maker's private data and more + // specifically of panasonic. So if the relevant panasonic data is not present we just skip it. + // Assumption here is that if its not present in ClipExtension then it will not be in Playlist extension + if ( ! avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPanasonicPrivateData.mPresent ) return true; mplFile.Seek ( dataBlockStart + extensionData.mMakersPrivateDataStartAddress, kXMP_SeekFromStart ); - + + // Here private data was found.If the data was panasonic private data and we were unable to read it , + // Return false if ( ! ReadAVCHDMakersPrivateData ( mplFile, playlistMarkID, extensionData.mMakersPrivateData ) ) return false; } @@ -1450,10 +1467,10 @@ static bool ReadAVCHDLegacyPlaylistFile ( const std::string& mplPath, static bool FindAVCHDLegacyPlaylistFile ( const std::string& strRootPath, const std::string& strClipName, - AVCHD_LegacyMetadata& avchdLegacyData ) + AVCHD_LegacyMetadata& avchdLegacyData, + std::string &mplPath ) { 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 @@ -1510,12 +1527,13 @@ static bool FindAVCHDLegacyPlaylistFile ( const std::string& strRootPath, static bool ReadAVCHDLegacyMetadata ( const std::string& strPath, const std::string& strRootPath, const std::string& strClipName, - AVCHD_LegacyMetadata& avchdLegacyData ) + AVCHD_LegacyMetadata& avchdLegacyData, + std::string& mplFile) { bool success = ReadAVCHDLegacyClipFile ( strPath, avchdLegacyData ); if ( success && avchdLegacyData.mClipExtensionData.mPresent ) { - success = FindAVCHDLegacyPlaylistFile ( strRootPath, strClipName, avchdLegacyData ); + success = FindAVCHDLegacyPlaylistFile ( strRootPath, strClipName, avchdLegacyData, mplFile ); } return success; @@ -1820,7 +1838,7 @@ AVCHD_MetaHandler::AVCHD_MetaHandler ( XMPFiles * _parent ) if ( this->parent->tempPtr == 0 ) { // The CheckFormat call might have been skipped. - this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath ); + this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() ); } this->rootPath.assign ( (char*) this->parent->tempPtr ); @@ -1984,6 +2002,130 @@ bool AVCHD_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) } // AVCHD_MetaHandler::GetFileModDate // ================================================================================================= +// AVCHD_MetaHandler::FillMetadataFiles +// ================================ +void AVCHD_MetaHandler::FillMetadataFiles ( std::vector<std::string> * metadataFiles) +{ + std::string noExtPath, filePath, altPath; + + noExtPath = rootPath + kDirChar + "BDMV" + kDirChar + "STREAM" + kDirChar + clipName; + filePath = noExtPath + ".xmp"; + if ( ! Host_IO::Exists ( filePath.c_str() ) ) { + altPath = noExtPath + ".XMP"; + if ( Host_IO::Exists ( altPath.c_str() ) ) filePath = altPath; + } + metadataFiles->push_back ( filePath ); + + noExtPath = rootPath + kDirChar + "BDMV" + kDirChar + "CLIPINF" + kDirChar + clipName; + filePath = noExtPath + ".clpi"; + if ( ! Host_IO::Exists ( filePath.c_str() ) ) { + altPath = noExtPath + ".CLPI"; + if ( ! Host_IO::Exists ( altPath.c_str() ) ) altPath = noExtPath + ".cpi"; + if ( ! Host_IO::Exists ( altPath.c_str() ) ) altPath = noExtPath + ".CPI"; + if ( Host_IO::Exists ( altPath.c_str() ) ) filePath = altPath; + } + metadataFiles->push_back ( filePath ); + +} // FillMetadataFiles_AVCHD + +// ================================================================================================= +// AVCHD_MetaHandler::IsMetadataWritable +// ======================================= + +bool AVCHD_MetaHandler::IsMetadataWritable ( ) +{ + std::vector<std::string> metadataFiles; + FillMetadataFiles(&metadataFiles); + std::vector<std::string>::iterator itr = metadataFiles.begin(); + // Check whether sidecar is writable, if not then check if it can be created. + return Host_IO::Writable( itr->c_str(), true ); +}// AVCHD_MetaHandler::IsMetadataWritable + +// ================================================================================================= +// AVCHD_MetaHandler::FillAssociatedResources +// ====================================== +void AVCHD_MetaHandler::FillAssociatedResources ( std::vector<std::string> * resourceList ) +{ + /// The possible associated resources: + /// BDMV/ + /// index.bdmv + /// MovieObject.bdmv + /// PLAYLIST/ + /// xxxxx.mpls + /// STREAM/ + /// zzzzz.m2ts + /// zzzzz.xmp + /// CLIPINF/ + /// zzzzz.clpi + /// BACKUP/ + // xxxxx is a five digit playlist name + // zzzzz is a five digit clip name + // + std::string bdmvPath = rootPath + kDirChar + "BDMV" + kDirChar; + std::string filePath, clipInfoPath; + //Add RootPath + filePath = rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists( resourceList, filePath ); + // Add existing files under the folder "BDMV" + filePath = bdmvPath + "index.bdmv"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "INDEX.BDMV"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "index.bdm"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "INDEX.BDM"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + } + } + filePath = bdmvPath + "MovieObject.bdmv"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "MOVIEOBJECT.BDMV"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "MovieObj.bdm"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "MOVIEOBJ.BDM"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + } + } + + + if ( MakeClipInfoPath ( &filePath, ".clpi", true /* checkFile */ ) ) { + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + clipInfoPath = filePath; + } + else { + filePath = bdmvPath + "CLIPINF" + kDirChar ; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + + bool addedStreamDir=false; + if ( MakeClipStreamPath ( &filePath, ".xmp", true /* checkFile */ ) ) { + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + addedStreamDir = true; + } + + + if ( MakeClipStreamPath ( &filePath, ".m2ts", true /* checkFile */ ) ) { + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + else if ( ! addedStreamDir ) { + filePath = bdmvPath + "STREAM" + kDirChar ; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + + AVCHD_LegacyMetadata avchdLegacyData; + if ( ReadAVCHDLegacyMetadata ( clipInfoPath, this->rootPath, this->clipName, avchdLegacyData, filePath ) ) { + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + else { + filePath = bdmvPath + "PLAYLIST" + kDirChar ; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } +} + +// ================================================================================================= // AVCHD_MetaHandler::CacheFileData // ================================ @@ -1992,22 +2134,24 @@ 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 ); + XMP_Throw ( "AVCHD 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; + if ( ! found ) return; // No XMP. + XMP_Assert ( Host_IO::Exists ( xmpPath.c_str() ) ); // MakeClipStreamPath should ensure this. - // Read the entire .XMP file. + // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 + // only if the file does not exist. 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. + if ( xmpFile == 0 ) XMP_Throw ( "AVCHD XMP file open failure", kXMPErr_InternalFailure ); this->parent->ioRef = xmpFile; XMP_Int64 xmpLen = xmpFile->Length(); @@ -2043,10 +2187,10 @@ void AVCHD_MetaHandler::ProcessXMP() // read clip info AVCHD_LegacyMetadata avchdLegacyData; - std::string strPath; + std::string strPath,mplfile; bool ok = this->MakeClipInfoPath ( &strPath, ".clpi", true /* checkFile */ ); - if ( ok ) ReadAVCHDLegacyMetadata ( strPath, this->rootPath, this->clipName, avchdLegacyData ); + if ( ok ) ReadAVCHDLegacyMetadata ( strPath, this->rootPath, this->clipName, avchdLegacyData , mplfile); if ( ! ok ) return; const AVCHD_blkPlayListMarkExt& markExt = avchdLegacyData.mPlaylistExtensionData.mPlaylistMarkExt; diff --git a/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp b/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp index 46c417e..0860d5b 100644 --- a/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp +++ b/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp @@ -49,6 +49,9 @@ class AVCHD_MetaHandler : public XMPFileHandler public: bool GetFileModDate ( XMP_DateTime * modDate ); + void FillMetadataFiles ( std::vector<std::string> * metadataFiles ); + void FillAssociatedResources ( std::vector<std::string> * resourceList ); + bool IsMetadataWritable ( ); void CacheFileData(); void ProcessXMP(); diff --git a/XMPFiles/source/FileHandlers/FLV_Handler.cpp b/XMPFiles/source/FileHandlers/FLV_Handler.cpp index 8c1c745..db6ae4b 100644 --- a/XMPFiles/source/FileHandlers/FLV_Handler.cpp +++ b/XMPFiles/source/FileHandlers/FLV_Handler.cpp @@ -551,9 +551,12 @@ void FLV_MetaHandler::UpdateFile ( bool doSafeUpdate ) // Rewrite the packet in-place if it fits. Otherwise rewrite the whole file. if ( this->xmpPacket.size() == (size_t)this->packetInfo.length ) { - + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)this->xmpPacket.size() ); fileRef->Seek ( this->packetInfo.offset, kXMP_SeekFromStart ); fileRef->Write ( this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() ); + if ( progressTracker != 0 ) progressTracker->WorkComplete(); + } else { @@ -675,7 +678,29 @@ void FLV_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) originalRef->Rewind(); tempRef->Rewind(); tempRef->Truncate ( 0 ); - + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) { + float fileSize=(float)(this->xmpPacket.size()+48); + if ( this->omdTagPos == 0 ) { + sourcePos=(this->flvHeaderLen+4); + fileSize+=sourcePos; + } else { + if ( this->xmpTagPos < this->omdTagPos ) { + fileSize+=this->xmpTagPos; + } + fileSize+=(this->omdTagPos + this->omdTagLen- + ((this->xmpTagPos!=0 && this->xmpTagPos < this->omdTagPos)? + (this->xmpTagPos + this->xmpTagLen):0)); + sourcePos =this->omdTagPos + this->omdTagLen; + } + if ( (this->xmpTagPos != 0) && (this->xmpTagPos >= sourcePos) ) { + fileSize+=(this->xmpTagPos - sourcePos); + sourcePos=this->xmpTagPos + this->xmpTagLen; + } + fileSize+=(sourceLen - sourcePos); + sourcePos=0; + progressTracker->BeginWork ( fileSize ); + } // First do whatever is needed to put the new XMP after any existing onMetaData tag, or as the // first time 0 tag. @@ -729,6 +754,8 @@ void FLV_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) XIO::Copy ( originalRef, tempRef, (sourceLen - sourcePos), abortProc, abortArg ); this->needsUpdate = false; + + if ( progressTracker != 0 ) progressTracker->WorkComplete(); } // FLV_MetaHandler::WriteTempFile diff --git a/XMPFiles/source/FileHandlers/FLV_Handler.hpp b/XMPFiles/source/FileHandlers/FLV_Handler.hpp index 7a63b5c..fe129de 100644 --- a/XMPFiles/source/FileHandlers/FLV_Handler.hpp +++ b/XMPFiles/source/FileHandlers/FLV_Handler.hpp @@ -40,7 +40,8 @@ static const XMP_OptionBits kFLV_HandlerFlags = ( kXMPFiles_CanInjectXMP | kXMPFiles_CanReconcile | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | - kXMPFiles_AllowsSafeUpdate + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress ); class FLV_MetaHandler : public XMPFileHandler diff --git a/XMPFiles/source/FileHandlers/InDesign_Handler.cpp b/XMPFiles/source/FileHandlers/InDesign_Handler.cpp index 5031cef..d627580 100644 --- a/XMPFiles/source/FileHandlers/InDesign_Handler.cpp +++ b/XMPFiles/source/FileHandlers/InDesign_Handler.cpp @@ -176,7 +176,10 @@ void InDesign_MetaHandler::CacheFileData() XMP_IO* fileRef = this->parent->ioRef; XMP_PacketInfo & packetInfo = this->packetInfo; - IOBuffer ioBuf; + XMP_Assert ( kINDD_PageSize == sizeof(InDesignMasterPage) ); + static const size_t kBufferSize = (2 * kINDD_PageSize); + XMP_Uns8 buffer [kBufferSize]; + size_t dbPages; XMP_Uns8 cobjEndian; @@ -184,19 +187,16 @@ void InDesign_MetaHandler::CacheFileData() 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 ); + fileRef->Rewind(); + fileRef->ReadAll ( buffer, (2 * kINDD_PageSize) ); - InDesignMasterPage * masters = (InDesignMasterPage *) ioBuf.ptr; + InDesignMasterPage * masters = (InDesignMasterPage *) &buffer[0]; XMP_Uns64 seq0 = GetUns64LE ( (XMP_Uns8 *) &masters[0].fSequenceNumber ); XMP_Uns64 seq1 = GetUns64LE ( (XMP_Uns8 *) &masters[1].fSequenceNumber ); @@ -212,9 +212,11 @@ void InDesign_MetaHandler::CacheFileData() 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. + // Look for the XMP contiguous object. Each contiguous object has a header and trailer, both of + // the InDesignContigObjMarker structure. The stream size in the header/trailer is the number of + // data bytes between the header and trailer. The XMP stream begins with a 4 byte size of the + // XMP packet. Yes, this is the contiguous object data size minus 4 - silly but true. The XMP + // must have a packet wrapper, the leading xpacket PI is used as the marker of XMP. XMP_Int64 cobjPos = (XMP_Int64)dbPages * kINDD_PageSize; // ! Use a 64 bit multiply! cobjPos -= (2 * sizeof(InDesignContigObjMarker)); // ! For the first pass in the loop. @@ -230,72 +232,73 @@ void InDesign_MetaHandler::CacheFileData() // ! 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. + fileRef->Seek ( cobjPos, kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, sizeof(InDesignContigObjMarker) ); - const InDesignContigObjMarker * cobjHeader = (const InDesignContigObjMarker *) ioBuf.ptr; + const InDesignContigObjMarker * cobjHeader = (const InDesignContigObjMarker *) &buffer[0]; 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. + // See if this is the XMP stream. - if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) continue; // Too small, can't possibly be XMP. + if ( streamLength < (4 + kUTF8_PacketHeaderLen + kUTF8_PacketTrailerLen) ) continue; // Too small, can't possibly be XMP. - XMP_Uns32 innerLength = GetUns32LE ( ioBuf.ptr ); - if ( this->streamBigEndian ) innerLength = GetUns32BE ( ioBuf.ptr ); + fileRef->ReadAll ( buffer, (4 + kUTF8_PacketHeaderLen) ); + XMP_Uns32 innerLength = GetUns32LE ( &buffer[0] ); + if ( this->streamBigEndian ) innerLength = GetUns32BE ( &buffer[0] ); 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 * chPtr = &buffer[4]; + size_t startLen = strlen((char*)kUTF8_PacketStart); + size_t idLen = strlen((char*)kUTF8_PacketID); + + if ( ! CheckBytes ( chPtr, kUTF8_PacketStart, startLen ) ) continue; + chPtr += startLen; - XMP_Uns8 quote = *ioBuf.ptr; + XMP_Uns8 quote = *chPtr; if ( (quote != '\'') && (quote != '"') ) continue; - ioBuf.ptr += 1; - if ( *ioBuf.ptr != quote ) { - if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr("\xEF\xBB\xBF"), 3 ) ) continue; - ioBuf.ptr += 3; + chPtr += 1; + if ( *chPtr != quote ) { + if ( ! CheckBytes ( chPtr, Uns8Ptr("\xEF\xBB\xBF"), 3 ) ) continue; + chPtr += 3; } - if ( *ioBuf.ptr != quote ) continue; - ioBuf.ptr += 1; + if ( *chPtr != quote ) continue; + chPtr += 1; - if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(" id="), 4 ) ) continue; - ioBuf.ptr += 4; - quote = *ioBuf.ptr; + if ( ! CheckBytes ( chPtr, Uns8Ptr(" id="), 4 ) ) continue; + chPtr += 4; + quote = *chPtr; 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; + chPtr += 1; + if ( ! CheckBytes ( chPtr, kUTF8_PacketID, idLen ) ) continue; + chPtr += idLen; + if ( *chPtr != quote ) continue; + chPtr += 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. + // total size of remaining contiguous objects, the trailingContentSize. We don't use the + // size to EOF, that would wrongly include the final zero padding for 4KB alignment. 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; + XMP_Uns32 len = fileRef->Read ( buffer, sizeof(InDesignContigObjMarker) ); + if ( len < sizeof(InDesignContigObjMarker) ) break; // Too small, must be end of file. + cobjHeader = (const InDesignContigObjMarker *) &buffer[0]; if ( ! CheckBytes ( Uns8Ptr(&cobjHeader->fGUID), kINDDContigObjHeaderGUID, kInDesignGUIDSize ) ) break; // Not a contiguous object header. streamLength = GetUns32LE ( (XMP_Uns8 *) &cobjHeader->fStreamLength ); } diff --git a/XMPFiles/source/FileHandlers/JPEG_Handler.cpp b/XMPFiles/source/FileHandlers/JPEG_Handler.cpp index 2cc4c31..ea025bd 100644 --- a/XMPFiles/source/FileHandlers/JPEG_Handler.cpp +++ b/XMPFiles/source/FileHandlers/JPEG_Handler.cpp @@ -34,8 +34,8 @@ using namespace std; /// // ================================================================================================= -static const char * kExifSignatureString = "Exif\0\x00"; -static const char * kExifSignatureAltStr = "Exif\0\xFF"; +static const char * kExifSignatureString = "Exif\0\x00"; // There are supposed to be two zero bytes, +static const char * kExifSignatureAltStr = "Exif\0\xFF"; // but files have been seen with just one. static const size_t kExifSignatureLength = 6; static const size_t kExifMaxDataLength = 0xFFFF - 2 - kExifSignatureLength; @@ -89,26 +89,28 @@ XMPFileHandler * JPEG_MetaHandlerCTor ( XMPFiles * parent ) bool JPEG_CheckFormat ( XMP_FileFormat format, XMP_StringPtr filePath, - XMP_IO* fileRef, + 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. + XMP_Uns8 buffer [100]; + XMP_Uns16 marker; - // 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. + fileRef->Rewind(); + if ( fileRef->Length() < 2 ) return false; // Need at least the SOI marker. + size_t bufferLen = fileRef->Read ( buffer, sizeof(buffer) ); - 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; + marker = GetUns16BE ( &buffer[0] ); + if ( marker != 0xFFD8 ) return false; // Offset 0 must have the SOI marker. + + // Skip 0xFF padding and high order 0xFF of next marker. + size_t bufferPos = 2; + while ( (bufferPos < bufferLen) && (buffer[bufferPos] == 0xFF) ) bufferPos += 1; + if ( bufferPos == bufferLen ) return true; // Nothing but 0xFF bytes, close enough. - XMP_Uns8 id = *ioBuf.ptr; + XMP_Uns8 id = buffer[bufferPos]; // Check the ID of the second marker. 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; @@ -142,6 +144,87 @@ JPEG_MetaHandler::~JPEG_MetaHandler() } // JPEG_MetaHandler::~JPEG_MetaHandler // ================================================================================================= +// CacheExtendedXMP +// ================ + +static void CacheExtendedXMP ( ExtendedXMPInfo * extXMP, XMP_Uns8 * buffer, size_t bufferLen ) +{ + + // Have 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. + + // The extended XMP JPEG marker segment content holds: + // - a signature string, "http://ns.adobe.com/xmp/extension/\0", already verified + // - a 128 bit GUID stored as a 32 byte ASCII hex string + // - a UInt32 full length of the entire extended XMP + // - a UInt32 offset for this portion of the extended XMP + // - the UTF-8 text for this portion of the extended XMP + + if ( bufferLen < kExtXMPPrefixLength ) return; // Ignore bad input. + XMP_Assert ( CheckBytes ( &buffer[0], kExtXMPSignatureString, kExtXMPSignatureLength ) ); + + XMP_Uns8 * bufferPtr = buffer + kExtXMPSignatureLength; // Start at the GUID. + + JPEG_MetaHandler::GUID_32 guid; + XMP_Assert ( sizeof(guid.data) == 32 ); + memcpy ( &guid.data[0], bufferPtr, sizeof(guid.data) ); // AUDIT: Use of sizeof(guid.data) is safe. + + bufferPtr += sizeof(guid.data); // Move to the length and offset. + XMP_Uns32 fullLen = GetUns32BE ( bufferPtr ); + XMP_Uns32 offset = GetUns32BE ( bufferPtr+4 ); + + bufferPtr += 8; // Move to the XMP stream portion. + size_t xmpLen = bufferLen - kExtXMPPrefixLength; + + #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; + extPortion.append ( (XMP_StringPtr)bufferPtr, xmpLen ); + +} // CacheExtendedXMP + +// ================================================================================================= // JPEG_MetaHandler::CacheFileData // =============================== // @@ -187,12 +270,11 @@ JPEG_MetaHandler::~JPEG_MetaHandler() void JPEG_MetaHandler::CacheFileData() { - XMP_IO* fileRef = this->parent->ioRef; + XMP_IO* fileRef = this->parent->ioRef; XMP_PacketInfo & packetInfo = this->packetInfo; - size_t segLen; - bool ok; - IOBuffer ioBuf; + static const size_t kBufferSize = 64*1024; // Enough for maximum segment contents. + XMP_Uns8 buffer [kBufferSize]; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; @@ -211,9 +293,7 @@ void JPEG_MetaHandler::CacheFileData() // 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 ); + fileRef->Seek ( 2, kXMP_SeekFromStart ); // Skip the SOI, CheckFormat made sure it is present. while ( true ) { @@ -221,238 +301,95 @@ void JPEG_MetaHandler::CacheFileData() 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; + if ( ! XIO::CheckFileSpace ( fileRef, 2 ) ) return; // Quit, don't throw, if the file ends unexpectedly. + + XMP_Uns16 marker = XIO::ReadUns16_BE ( fileRef ); // Read the next marker. + if ( marker == 0xFFFF ) { + // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. + fileRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. + continue; } - 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. + XMP_Uns16 contentLen = XIO::ReadUns16_BE ( fileRef ); // Read this segment's length. + if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); + contentLen -= 2; // Reduce to just the content length. + + XMP_Int64 contentOrigin = fileRef->Offset(); + size_t signatureLen; - ok = CheckFileSpace ( fileRef, &ioBuf, kPSIRSignatureLength ); - if ( ok && (segLen >= kPSIRSignatureLength) && - CheckBytes ( ioBuf.ptr, kPSIRSignatureString, kPSIRSignatureLength ) ) { + if ( (marker == 0xFFED) && (contentLen >= 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 an APP13 marker, is it the Photoshop image resources? - // This is the not Photoshop image resources, skip the marker segment's content. + signatureLen = fileRef->Read ( buffer, kPSIRSignatureLength ); + if ( (signatureLen == kPSIRSignatureLength) && + CheckBytes ( &buffer[0], kPSIRSignatureString, kPSIRSignatureLength ) ) { - 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 ); - } + size_t psirLen = contentLen - kPSIRSignatureLength; + fileRef->Seek ( (contentOrigin + kPSIRSignatureLength), kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, psirLen ); + this->psirContents.assign ( (char*)buffer, psirLen ); + continue; // Move on to the next marker. } - continue; // Move on to the next marker. - - } else if ( marker == 0xFFE1 ) { + } else if ( (marker == 0xFFE1) && (contentLen >= kExifSignatureLength) ) { // Check for the shortest signature. // 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; - + // ! Check in that order, which is in increasing signature string length. + + XMP_Assert ( (kExifSignatureLength < kMainXMPSignatureLength) && + (kMainXMPSignatureLength < kExtXMPSignatureLength) ); + signatureLen = fileRef->Read ( buffer, kExtXMPSignatureLength ); // Read for the longest signature. + + if ( (signatureLen >= kExifSignatureLength) && + (CheckBytes ( &buffer[0], kExifSignatureString, kExifSignatureLength ) || + CheckBytes ( &buffer[0], kExifSignatureAltStr, kExifSignatureLength )) ) { + + size_t exifLen = contentLen - kExifSignatureLength; + fileRef->Seek ( (contentOrigin + kExifSignatureLength), kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, exifLen ); + this->exifContents.assign ( (char*)buffer, exifLen ); continue; // Move on to the next marker. } + + if ( (signatureLen >= kMainXMPSignatureLength) && + CheckBytes ( &buffer[0], kMainXMPSignatureString, kMainXMPSignatureLength ) ) { - // 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->containsXMP = true; // Found the standard XMP packet. + size_t xmpLen = contentLen - kMainXMPSignatureLength; + fileRef->Seek ( (contentOrigin + kMainXMPSignatureLength), kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, xmpLen ); + this->xmpPacket.assign ( (char*)buffer, xmpLen ); + this->packetInfo.offset = contentOrigin + kMainXMPSignatureLength; + this->packetInfo.length = (XMP_Int32)xmpLen; + this->packetInfo.padSize = 0; // Assume the rest for now, set later 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. } + + if ( (signatureLen >= kExtXMPSignatureLength) && + CheckBytes ( &buffer[0], kExtXMPSignatureString, kExtXMPSignatureLength ) ) { - // 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; - + fileRef->Seek ( contentOrigin, kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, contentLen ); + CacheExtendedXMP ( &extXMP, buffer, contentLen ); 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. - } + + // None of the above, seek to the next marker. + fileRef->Seek ( (contentOrigin + contentLen) , kXMP_SeekFromStart ); } @@ -610,6 +547,8 @@ void JPEG_MetaHandler::ProcessXMP() this->psirMgr = new PSIR_FileWriter(); this->iptcMgr = new IPTC_Writer(); // ! Parse it later. } + if ( this->parent ) + exifMgr->SetErrorCallback( &this->parent->errorCallback ); // 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. @@ -666,14 +605,8 @@ void JPEG_MetaHandler::ProcessXMP() 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. - } + } catch ( ... ) { /* Ignore parsing failures, someday we hope to get partial XMP back. */ } + haveXMP = true; } // Process the extended XMP if it has a matching GUID. @@ -822,8 +755,6 @@ void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) // 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; @@ -832,16 +763,16 @@ void JPEG_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) 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. + XMP_Uns16 marker, contentLen; - if ( origRef->Length() == 0 ) return; // Tolerate empty files. - origRef->Rewind(); - tempRef->Truncate ( 0 ); + static const size_t kBufferSize = 64*1024; // Enough for a segment with maximum contents. + XMP_Uns8 buffer [kBufferSize]; + + XMP_Int64 origLength = origRef->Length(); + if ( origLength == 0 ) return; // Tolerate empty files. + if ( origLength < 4 ) { + XMP_Throw ( "JPEG must have at least SOI and EOI markers", kXMPErr_BadJPEG ); + } if ( ! skipReconcile ) { // Update the IPTC-IIM and native TIFF/Exif metadata, and reserialize the now final XMP packet. @@ -849,46 +780,47 @@ void JPEG_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) 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 ); - } + origRef->Rewind(); + tempRef->Truncate ( 0 ); - marker = GetUns16BE ( ioBuf.ptr ); + marker = XIO::ReadUns16_BE ( origRef ); // Just read the SOI marker. if ( marker != 0xFFD8 ) XMP_Throw ( "Missing SOI marker", kXMPErr_BadJPEG ); - tempRef->Write ( ioBuf.ptr, 2 ); - ioBuf.ptr += 2; + XIO::WriteUns16_BE ( tempRef, marker ); - // Copy the leading APP0 marker segments. + // Copy any 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 ( ! XIO::CheckFileSpace ( origRef, 2 ) ) break; // Tolerate a file that ends abruptly. + + marker = XIO::ReadUns16_BE ( origRef ); // Read the next marker. if ( marker == 0xFFFF ) { - tempRef->Write ( ioBuf.ptr, 1 ); // Copy the 0xFF pad byte. - ++ioBuf.ptr; + // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. + origRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. 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 ( marker != 0xFFE0 ) break; // Have a non-APP0 marker. + XIO::WriteUns16_BE ( tempRef, marker ); // Write the APP0 marker. + + contentLen = XIO::ReadUns16_BE ( origRef ); // Copy the APP0 segment's length. + XIO::WriteUns16_BE ( tempRef, contentLen ); - if ( ! CheckFileSpace ( origRef, &ioBuf, segLen ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG ); - tempRef->Write ( ioBuf.ptr, (XMP_Int32)segLen ); - ioBuf.ptr += segLen; + if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); + contentLen -= 2; // Reduce to just the content length. + origRef->ReadAll ( buffer, contentLen ); // Copy the APP0 segment's content. + tempRef->Write ( buffer, contentLen ); } // Write the new Exif APP1 marker segment. + XMP_Uns32 first4; + if ( this->exifMgr != 0 ) { void* exifPtr; @@ -965,73 +897,91 @@ void JPEG_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) // Copy remaining marker segments, skipping old metadata, to the first SOS marker or to EOI. + origRef->Seek ( -2, kXMP_SeekFromCurrent ); // Back up to the marker from the end of the APP0 copy loop. + 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 ( ! XIO::CheckFileSpace ( origRef, 2 ) ) break; // Tolerate a file that ends abruptly. + + marker = XIO::ReadUns16_BE ( origRef ); // Read the next marker. if ( marker == 0xFFFF ) { - tempRef->Write ( ioBuf.ptr, 1 ); // Copy the 0xFF pad byte. - ++ioBuf.ptr; + // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. + origRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. continue; } - if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) break; // Quit at the first SOS marker or at EOI. + if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) { // Quit at the first SOS marker or at EOI. + origRef->Seek ( -2, kXMP_SeekFromCurrent ); // The tail copy must include this marker. + break; + } 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 ); - + contentLen = XIO::ReadUns16_BE ( origRef ); // Read this segment's length. + if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); + contentLen -= 2; // Reduce to just the content length. + + XMP_Int64 contentOrigin = origRef->Offset(); bool copySegment = true; - XMP_Uns8* signaturePtr = ioBuf.ptr + 4; + size_t signatureLen; + + if ( (marker == 0xFFED) && (contentLen >= kPSIRSignatureLength) ) { - if ( marker == 0xFFED ) { - if ( (segLen >= kPSIRSignatureLength) && - CheckBytes ( signaturePtr, kPSIRSignatureString, kPSIRSignatureLength ) ) { + // This is an APP13 segment, skip if it is the old PSIR. + signatureLen = origRef->Read ( buffer, kPSIRSignatureLength ); + if ( (signatureLen == kPSIRSignatureLength) && + CheckBytes ( &buffer[0], kPSIRSignatureString, kPSIRSignatureLength ) ) { copySegment = false; } - } else if ( marker == 0xFFE1 ) { - if ( (segLen >= kExifSignatureLength) && - (CheckBytes ( signaturePtr, kExifSignatureString, kExifSignatureLength ) || - CheckBytes ( signaturePtr, kExifSignatureAltStr, kExifSignatureLength )) ) { + + } else if ( (marker == 0xFFE1) && (contentLen >= kExifSignatureLength) ) { // Check for the shortest signature. + + // This is an APP1 segment, skip if it is the old Exif or XMP. + + XMP_Assert ( (kExifSignatureLength < kMainXMPSignatureLength) && + (kMainXMPSignatureLength < kExtXMPSignatureLength) ); + signatureLen = origRef->Read ( buffer, kExtXMPSignatureLength ); // Read for the longest signature. + + if ( (signatureLen >= kExifSignatureLength) && + (CheckBytes ( &buffer[0], kExifSignatureString, kExifSignatureLength ) || + CheckBytes ( &buffer[0], kExifSignatureAltStr, kExifSignatureLength )) ) { copySegment = false; - } else if ( (segLen >= kMainXMPSignatureLength) && - CheckBytes ( signaturePtr, kMainXMPSignatureString, kMainXMPSignatureLength ) ) { + } + + if ( copySegment && (signatureLen >= kMainXMPSignatureLength) && + CheckBytes ( &buffer[0], kMainXMPSignatureString, kMainXMPSignatureLength ) ) { copySegment = false; - } else if ( (segLen >= kExtXMPPrefixLength) && - CheckBytes ( signaturePtr, kExtXMPSignatureString, kExtXMPSignatureLength ) ) { + } + + if ( copySegment && (signatureLen == kExtXMPSignatureLength) && + CheckBytes ( &buffer[0], kExtXMPSignatureString, kExtXMPPrefixLength ) ) { copySegment = false; } + + } + + if ( ! copySegment ) { + origRef->Seek ( (contentOrigin + contentLen), kXMP_SeekFromStart ); + } else { + XIO::WriteUns16_BE ( tempRef, marker ); + XIO::WriteUns16_BE ( tempRef, (contentLen + 2) ); + origRef->Seek ( contentOrigin, kXMP_SeekFromStart ); + origRef->ReadAll ( buffer, contentLen ); + tempRef->Write ( buffer, contentLen ); } - - 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; - } - + XIO::Copy ( origRef, tempRef, (origLength - origRef->Offset()) ); this->needsUpdate = false; } // JPEG_MetaHandler::WriteTempFile diff --git a/XMPFiles/source/FileHandlers/JPEG_Handler.hpp b/XMPFiles/source/FileHandlers/JPEG_Handler.hpp index 568ee1b..9e859c1 100644 --- a/XMPFiles/source/FileHandlers/JPEG_Handler.hpp +++ b/XMPFiles/source/FileHandlers/JPEG_Handler.hpp @@ -90,6 +90,7 @@ private: typedef std::map < GUID_32, std::string > ExtendedXMPMap; ExtendedXMPMap extendedXMP; // ! Only contains those with complete data. +// void CacheExtendedXMP ( ExtendedXMPInfo * extXMP, XMP_Uns8 * buffer, size_t bufferLen ); }; // JPEG_MetaHandler diff --git a/XMPFiles/source/FileHandlers/MP3_Handler.cpp b/XMPFiles/source/FileHandlers/MP3_Handler.cpp index 47f3aae..a568c1a 100644 --- a/XMPFiles/source/FileHandlers/MP3_Handler.cpp +++ b/XMPFiles/source/FileHandlers/MP3_Handler.cpp @@ -13,6 +13,7 @@ #include "public/include/XMP_IO.hpp" #include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" #include "source/XMPFiles_IO.hpp" #include "source/XIO.hpp" @@ -232,7 +233,7 @@ void MP3_MetaHandler::CacheFileData() // 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; + if ( (newPos > this->oldTagSize) || (spaceLeft < (XMP_Int64)ID3Header::kID3_TagHeaderSize) ) break; } @@ -309,8 +310,8 @@ void MP3_MetaHandler::ProcessXMP() // go deal with it! // get the property - std::string utf8string; - bool result = curFrame->getFrameValue ( this->majorVersion, logicalID, &utf8string ); + std::string id3Text, xmpText; + bool result = curFrame->getFrameValue ( this->majorVersion, logicalID, &id3Text ); if ( ! result ) continue; //ignore but preserve this frame (i.e. not applicable COMM frame) ////////////////////////////////////////////////////////////////////////////////// @@ -334,41 +335,19 @@ void MP3_MetaHandler::ProcessXMP() this->xmpObj.SetProperty ( kXMP_NS_DM, "partOfCompilation", "true" ); - } else if ( ! utf8string.empty() ) { + } else if ( ! id3Text.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 ); + this->xmpObj.SetLocalizedText ( reconProps[r].ns, reconProps[r].prop,"", "x-default", id3Text ); 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 ); + case 0x54434F4E: // TCON -> genre + ID3_Support::GenreUtils::ConvertGenreToXMP ( id3Text.c_str(), &xmpText ); + if ( ! xmpText.empty() ) { + this->xmpObj.SetProperty ( reconProps[r].ns, reconProps[r].prop, xmpText ); } break; @@ -376,7 +355,7 @@ void MP3_MetaHandler::ProcessXMP() { try { // Don't let wrong dates in id3 stop import. if ( ! hasTDRC ) { - newDateTime.year = SXMPUtils::ConvertToInt ( utf8string ); + newDateTime.year = SXMPUtils::ConvertToInt ( id3Text ); newDateTime.hasDate = true; } } catch ( ... ) { @@ -390,9 +369,9 @@ void MP3_MetaHandler::ProcessXMP() 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)); + if ( (! hasTDRC) && (id3Text.length() == 4) ) { + newDateTime.day = SXMPUtils::ConvertToInt (id3Text.substr(0,2)); + newDateTime.month = SXMPUtils::ConvertToInt ( id3Text.substr(2,2)); newDateTime.hasDate = true; } } catch ( ... ) { @@ -406,9 +385,9 @@ void MP3_MetaHandler::ProcessXMP() 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)); + if ( (! hasTDRC) && (id3Text.length() == 4) ) { + newDateTime.hour = SXMPUtils::ConvertToInt (id3Text.substr(0,2)); + newDateTime.minute = SXMPUtils::ConvertToInt ( id3Text.substr(2,2)); newDateTime.hasTime = true; } } catch ( ... ) { @@ -422,7 +401,7 @@ void MP3_MetaHandler::ProcessXMP() try { // Don't let wrong dates in id3 stop import. hasTDRC = true; // This always wins over TYER, TDAT and TIME - SXMPUtils::ConvertToDate ( utf8string, &newDateTime ); + SXMPUtils::ConvertToDate ( id3Text, &newDateTime ); } catch ( ... ) { // Do nothing, let other imports proceed. } @@ -432,7 +411,7 @@ void MP3_MetaHandler::ProcessXMP() 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 ); + this->xmpObj.SetProperty ( reconProps[r].ns, reconProps[r].prop, id3Text ); break; }//switch @@ -497,7 +476,6 @@ void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) std::string value; bool needDescriptor = false; - bool need16LE = true; bool needEncodingByte = true; XMP_Uns32 logicalID = GetUns32BE ( reconProps[r].mainID ); @@ -512,7 +490,6 @@ void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) 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 { @@ -527,27 +504,14 @@ void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) 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; + bool found = xmpObj.GetProperty ( reconProps[r].ns, reconProps[r].prop, &value, 0 ); + if ( found ) { + std::string xmpValue = value; + ID3_Support::GenreUtils::ConvertGenreToID3 ( xmpValue.c_str(), &value ); } - // 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; @@ -617,13 +581,11 @@ void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) 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: @@ -646,11 +608,13 @@ void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) } // 3/4) no old value, create new value + bool needUTF16 = false; + if ( needEncodingByte ) needUTF16 = (! ReconcileUtils::IsASCII ( value.c_str(), value.size() ) ); if ( frame != 0 ) { - frame->setFrameValue( value, needDescriptor, need16LE, false, needEncodingByte ); + frame->setFrameValue( value, needDescriptor, needUTF16, false, needEncodingByte ); } else { ID3v2Frame* newFrame=new ID3v2Frame( storedID ); - newFrame->setFrameValue( value, needDescriptor, need16LE, false, needEncodingByte ); //always write as utf16-le incl. BOM + newFrame->setFrameValue( value, needDescriptor, needUTF16, false, needEncodingByte ); //always write as utf16-le incl. BOM framesVector.push_back( newFrame ); framesMap[ storedID ] = newFrame; continue; @@ -685,7 +649,7 @@ void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( framesVector[i]->active ) newFramesSize += (frameHeaderSize + framesVector[i]->contentSize); } - mustShift = (newFramesSize > (oldTagSize - ID3Header::kID3_TagHeaderSize)) || + mustShift = (newFramesSize > (XMP_Int64)(oldTagSize - ID3Header::kID3_TagHeaderSize)) || //optimization: If more than 8K can be saved by rewriting the MP3, go do it: ((newFramesSize + 8*1024) < oldTagSize ); diff --git a/XMPFiles/source/FileHandlers/MP3_Handler.hpp b/XMPFiles/source/FileHandlers/MP3_Handler.hpp index 261ca3e..05345c6 100644 --- a/XMPFiles/source/FileHandlers/MP3_Handler.hpp +++ b/XMPFiles/source/FileHandlers/MP3_Handler.hpp @@ -63,7 +63,7 @@ private: 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? + //bool legacyChanged; // tag rewrite certainly needed? ID3Header id3Header; diff --git a/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp b/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp index cd8512e..abeb781 100644 --- a/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp +++ b/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp @@ -19,9 +19,10 @@ #include "XMPFiles/source/XMPFiles_Impl.hpp" #include "source/XMPFiles_IO.hpp" #include "source/XIO.hpp" +#include "source/IOUtils.hpp" #include "XMPFiles/source/FileHandlers/MPEG2_Handler.hpp" - +#include "../FormatSupport/PackageFormat_Support.hpp" using namespace std; // ================================================================================================= @@ -96,6 +97,10 @@ MPEG2_MetaHandler::MPEG2_MetaHandler ( XMPFiles * _parent ) this->handlerFlags = kMPEG2_HandlerFlags; this->stdCharForm = kXMP_Char8Bit; + XMP_StringPtr filePath = this->parent->GetFilePath().c_str(); + XMP_StringPtr extPtr = FindFileExtension ( filePath ); + this->sidecarPath.assign ( filePath, (extPtr - filePath) ); + this->sidecarPath += ".xmp"; } // MPEG2_MetaHandler::MPEG2_MetaHandler // ================================================================================================= @@ -114,18 +119,29 @@ MPEG2_MetaHandler::~MPEG2_MetaHandler() 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::FillAssociatedResources +// ================================= +void MPEG2_MetaHandler::FillAssociatedResources ( std::vector<std::string> * resourceList ) +{ + resourceList->push_back(this->parent->GetFilePath()); + PackageFormat_Support::AddResourceIfExists(resourceList, this->sidecarPath); +} // MPEG2_MetaHandler::FillAssociatedResources + +// ================================================================================================= +// MPEG2_MetaHandler::IsMetadataWritable +// ================================= +bool MPEG2_MetaHandler::IsMetadataWritable ( ) +{ + return Host_IO::Writable( this->sidecarPath.c_str(), true ); +} // MPEG2_MetaHandler::IsMetadataWritable + +// ================================================================================================= // MPEG2_MetaHandler::CacheFileData // ================================ @@ -143,11 +159,6 @@ void MPEG2_MetaHandler::CacheFileData() // 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 ); diff --git a/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp b/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp index be6d38b..549781f 100644 --- a/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp +++ b/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp @@ -51,6 +51,8 @@ public: ~MPEG2_MetaHandler(); bool GetFileModDate ( XMP_DateTime * modDate ); + void FillAssociatedResources ( std::vector<std::string> * resourceList ); + bool IsMetadataWritable ( ) ; void CacheFileData(); diff --git a/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp b/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp index fad238f..2dfe088 100644 --- a/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp +++ b/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp @@ -21,6 +21,7 @@ #include "XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp" #include "XMPFiles/source/FormatSupport/MOOV_Support.hpp" +#include "source/XMP_ProgressTracker.hpp" #include "source/UnicodeConversions.hpp" #include "third-party/zuid/interfaces/MD5.h" @@ -40,7 +41,11 @@ using namespace std; // The basic content of a timecode sample description table entry. Does not include trailing boxes. +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( 1 ) +#else #pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 struct stsdBasicEntry { XMP_Uns32 entrySize; @@ -55,7 +60,11 @@ struct stsdBasicEntry { XMP_Uns8 reserved_3; }; +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( ) +#else #pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 // ================================================================================================= @@ -226,7 +235,8 @@ bool MPEG4_CheckFormat ( XMP_FileFormat format, 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) ) { + } else if ( (brand == ISOMedia::k_mp41) || (brand == ISOMedia::k_mp42) || + (brand == ISOMedia::k_f4v) || ( brand == ISOMedia::k_avc1 ) ) { haveCompatibleBrand = true; // Need to keep looking in case 'qt ' follows. } @@ -239,7 +249,6 @@ bool MPEG4_CheckFormat ( XMP_FileFormat format, 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. @@ -643,7 +652,7 @@ static void ExportISOCopyrights ( const SXMPMeta & xmp, MOOV_Manager * moovMgr ) MOOV_Manager::BoxInfo cprtInfo; MOOV_Manager::BoxRef cprtRef = moovMgr->GetNthChild ( udtaRef, ordinal-1, &cprtInfo ); - if ( (cprtRef == 0) ) break; // Sanity check, should not happen. + 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. @@ -729,7 +738,7 @@ static void ExportISOCopyrights ( const SXMPMeta & xmp, MOOV_Manager * moovMgr ) MOOV_Manager::BoxInfo cprtInfo; MOOV_Manager::BoxRef cprtRef = moovMgr->GetNthChild ( udtaRef, isoIndex, &cprtInfo ); - if ( (cprtRef == 0) ) break; // Sanity check, should not happen. + 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. @@ -1056,6 +1065,36 @@ static MOOV_Manager::BoxRef FindTimecode_trak ( const MOOV_Manager & moovMgr ) } // FindTimecode_trak // ================================================================================================= +// FindTimecode_dref +// ================= +// +// Look for the mdia/minf/dinf/dref box within a well-formed timecode track, return the dref box ref. + +static MOOV_Manager::BoxRef FindTimecode_dref ( const MOOV_Manager & moovMgr ) +{ + + MOOV_Manager::BoxRef trakRef = FindTimecode_trak ( moovMgr ); + if ( trakRef == 0 ) return 0; + + MOOV_Manager::BoxInfo tempInfo; + MOOV_Manager::BoxRef tempRef, drefRef; + + 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; + + tempRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_dinf, &tempInfo ); + if ( tempRef == 0 ) return 0; + + drefRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_dref, &tempInfo ); + + return drefRef; + +} // FindTimecode_dref + +// ================================================================================================= // FindTimecode_stbl // ================= // @@ -1343,7 +1382,11 @@ static void ExportTimecodeItems ( const SXMPMeta & xmp, MPEG4_MetaHandler::Timec // ImportCr8rItems // =============== +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( 1 ) +#else #pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 struct PrmLBoxContent { XMP_Uns32 magic; @@ -1370,7 +1413,11 @@ struct Cr8rBoxContent { char appName[32]; }; +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( ) +#else #pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 // ------------------------------------------------------------------------------------------------- @@ -2142,6 +2189,46 @@ void MPEG4_MetaHandler::ProcessXMP() bool MPEG4_MetaHandler::ParseTimecodeTrack() { + MOOV_Manager::BoxInfo drefInfo; + MOOV_Manager::BoxRef drefRef = FindTimecode_dref ( this->moovMgr ); + bool qtTimecodeIsExternal=false; + if( drefRef != 0 ) + { + this->moovMgr.GetBoxInfo( drefRef , &drefInfo ); + // After dref atom in a QT file we should only + // proceed further to check the Data refernces + // if the total size of the content is greater + // than 8 bytes which suggests that there is atleast + // one data reference to check for external references. + if ( drefInfo.contentSize>8) + { + XMP_Uns32 noOfDrefs=GetUns32BE(drefInfo.content+4); + if(noOfDrefs>0) + { + const XMP_Uns8* dataReference = drefInfo.content + 8; + const XMP_Uns8* nextDataref = 0; + const XMP_Uns8* boxlimit = drefInfo.content + drefInfo.contentSize; + ISOMedia::BoxInfo dataRefernceInfo; + while(noOfDrefs--) + { + nextDataref= ISOMedia::GetBoxInfo( dataReference , boxlimit, + &dataRefernceInfo); + //The content atleast contains the flag and some data + if ( dataRefernceInfo.contentSize > 4 ) + { + if (dataRefernceInfo.boxType==ISOMedia::k_alis && + *((XMP_Uns8*)(dataReference + dataRefernceInfo.headerSize + 4)) !=1 ) + { + qtTimecodeIsExternal=true; + break; + } + } + dataReference=nextDataref; + } + } + } + } + MOOV_Manager::BoxRef stblRef = FindTimecode_stbl ( this->moovMgr ); if ( stblRef == 0 ) return false; @@ -2217,91 +2304,98 @@ bool MPEG4_MetaHandler::ParseTimecodeTrack() } // Find the timecode sample. + // Read the timecode only if we are sure that it is not External + // This way we never find stsdBox and ExportTimecodeItems and + // ImportTimecodeItems doesn't do anything with timeCodeSample + // Also because sampleOffset is/remains zero UpdateFile doesn't + // update the timeCodeSample value + if(!qtTimecodeIsExternal) + { + 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. - 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 ) { + } else { - 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. + 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. - } 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 ( 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->GetFilePath().c_str(), Host_IO::openReadOnly, &this->parent->errorCallback); + XMP_Enforce ( localFile != 0 ); + this->parent->ioRef = localFile; + } - 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; + } - 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. + // 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); + 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::BoxRef elstRef = 0; + if ( isQT ) elstRef = FindTimecode_elst ( this->moovMgr ); + if ( elstRef != 0 ) { - MOOV_Manager::BoxInfo elstInfo; - this->moovMgr.GetBoxInfo ( elstRef, &elstInfo ); + 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); + 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. + // Finally update this->tmcdInfo to remember (for update) that there is an OK timecode track. - this->tmcdInfo.stsdBoxFound = true; - this->tmcdInfo.sampleOffset = sampleOffset; + this->tmcdInfo.stsdBoxFound = true; + this->tmcdInfo.sampleOffset = sampleOffset; + } return true; } // MPEG4_MetaHandler::ParseTimecodeTrack @@ -2484,6 +2578,21 @@ void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( ! haveISOFile ) ExportCr8rItems ( this->xmpObj, &this->moovMgr ); + // Set up progress tracking if necessary. At this point just include the XMP size, we don't + // know the 'moov' box size until later. + + bool localProgressTracking = false; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) { + float xmpSize = (float)this->xmpPacket.size(); + if ( progressTracker->WorkInProgress() ) { + progressTracker->AddTotalWork ( xmpSize ); + } else { + localProgressTracking = true; + progressTracker->BeginWork ( xmpSize ); + } + } + // 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. @@ -2506,6 +2615,7 @@ void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate ) 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 ); @@ -2520,7 +2630,11 @@ void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( this->moovMgr.IsChanged() ) { this->moovMgr.UpdateMemoryTree(); - this->UpdateTopLevelBox ( moovBoxPos, moovBoxSize, &this->moovMgr.fullSubtree[0], (XMP_Uns32)this->moovMgr.fullSubtree.size() ); + if ( progressTracker != 0 ) { + progressTracker->AddTotalWork ( (float)this->moovMgr.fullSubtree.size() ); + } + this->UpdateTopLevelBox ( moovBoxPos, moovBoxSize, &this->moovMgr.fullSubtree[0], + (XMP_Uns32)this->moovMgr.fullSubtree.size() ); } if ( this->tmcdInfo.sampleOffset != 0 ) { @@ -2546,6 +2660,8 @@ void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate ) } + if ( localProgressTracking ) progressTracker->WorkComplete(); + } // MPEG4_MetaHandler::UpdateFile // ================================================================================================= @@ -2561,11 +2677,13 @@ void MPEG4_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) XMP_Assert ( this->needsUpdate ); XMP_IO* originalRef = this->parent->ioRef; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; - originalRef->Rewind(); tempRef->Rewind(); + originalRef->Rewind(); + if ( progressTracker != 0 ) progressTracker->BeginWork ( (float) originalRef->Length() ); XIO::Copy ( originalRef, tempRef, originalRef->Length(), - this->parent->abortProc, this->parent->abortArg ); + this->parent->abortProc, this->parent->abortArg ); try { this->parent->ioRef = tempRef; // ! Fool UpdateFile into using the temp file. @@ -2576,6 +2694,8 @@ void MPEG4_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) throw; } + if ( progressTracker != 0 ) progressTracker->WorkComplete(); + } // MPEG4_MetaHandler::WriteTempFile // ================================================================================================= diff --git a/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp b/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp index 6474f0d..49c749f 100644 --- a/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp +++ b/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp @@ -37,7 +37,8 @@ static const XMP_OptionBits kMPEG4_HandlerFlags = ( kXMPFiles_CanInjectXMP | kXMPFiles_CanReconcile | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | - kXMPFiles_AllowsSafeUpdate + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress ); class MPEG4_MetaHandler : public XMPFileHandler diff --git a/XMPFiles/source/FileHandlers/P2_Handler.cpp b/XMPFiles/source/FileHandlers/P2_Handler.cpp index 295a7a1..8e692e9 100644 --- a/XMPFiles/source/FileHandlers/P2_Handler.cpp +++ b/XMPFiles/source/FileHandlers/P2_Handler.cpp @@ -15,9 +15,10 @@ #include "XMPFiles/source/XMPFiles_Impl.hpp" #include "source/XMPFiles_IO.hpp" #include "source/XIO.hpp" +#include "source/IOUtils.hpp" #include "XMPFiles/source/FileHandlers/P2_Handler.hpp" - +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" #include "third-party/zuid/interfaces/MD5.h" #include <cmath> @@ -273,7 +274,7 @@ P2_MetaHandler::P2_MetaHandler ( XMPFiles * _parent ) : expat(0), clipMetadata(0 if ( this->parent->tempPtr == 0 ) { // The CheckFormat call might have been skipped. - this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath ); + this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() ); } this->rootPath.assign ( (char*) this->parent->tempPtr ); @@ -909,29 +910,47 @@ void P2_MetaHandler::SetAltitudeFromLegacyXML ( XML_NodePtr legacyLocationConte // P2_MetaHandler::ForceChildElement // ================================= -XML_Node * P2_MetaHandler::ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, int indent /* = 0 */ ) +XML_Node * P2_MetaHandler::ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, XMP_Int32 indent , XMP_Bool insertAtFront ) { - XML_Node * wsNode; + XML_Node * wsNodeBefore, * wsNodeAfter; 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 ); + wsNodeBefore = new XML_Node ( parent, "", kCDataNode ); + wsNodeBefore->value = " "; // Add 2 spaces to the existing WS before the parent's close tag. 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 ); + wsNodeAfter = new XML_Node ( parent, "", kCDataNode ); + wsNodeAfter->value = '\n'; + for ( ; indent > 1; --indent ) wsNodeAfter->value += " "; // Indent less 1, to "outdent" the parent's close. + + if(insertAtFront){ + // we are asked to insert this child as the first child of it's parent.So if P is the parent and B,C are children + // already present. Then if we add a new child A as it's first child then we need to first add new line character right + // after "<P>" and then proper indentation to bring them on the level of other children. + //<P> + // <B> + // <C> + //</P> + std::vector<XML_Node *> indentedNode; + indentedNode.push_back(wsNodeAfter); + indentedNode.push_back(wsNodeBefore); + indentedNode.push_back(childNode); + parent->content.insert(parent->content.begin(), indentedNode.begin(), indentedNode.end()); + } + else{ + parent->content.push_back(wsNodeBefore); + parent->content.push_back(childNode); + parent->content.push_back(wsNodeAfter); + } } @@ -1086,6 +1105,103 @@ bool P2_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) } // P2_MetaHandler::GetFileModDate // ================================================================================================= +// P2_MetaHandler::FillMetadataFiles +// ================================= +void P2_MetaHandler::FillMetadataFiles ( std::vector<std::string>* metadataFiles ) +{ + std::string noExtPath, filePath; + + noExtPath = rootPath + kDirChar + "CONTENTS" + kDirChar + "CLIP" + kDirChar + clipName; + + filePath = noExtPath + ".XMP"; + metadataFiles->push_back ( filePath ); + filePath = noExtPath + ".XML"; + metadataFiles->push_back ( filePath ); + +} // FillMetadataFiles_P2 + +// ================================================================================================= +// P2_MetaHandler::IsMetadataWritable +// ======================================= + +bool P2_MetaHandler::IsMetadataWritable ( ) +{ + std::vector<std::string> metadataFiles; + FillMetadataFiles(&metadataFiles); + std::vector<std::string>::iterator itr = metadataFiles.begin(); + // Check whether sidecar is writable, if not then check if it can be created. + bool xmpWritable = Host_IO::Writable( itr->c_str(), true ); + // Check if legacy XML is writable. + bool xmlWritable = Host_IO::Writable( (++itr)->c_str(), false ); + return (xmlWritable && xmpWritable); +}// P2_MetaHandler::IsMetadataWritable + + +// ================================================================================================= +// P2_MetaHandler::FillAssociatedResources +// ====================================== +void P2_MetaHandler::FillAssociatedResources ( std::vector<std::string> * resourceList ) +{ + // The possible associated resources: + // CONTENTS/ + // CLIP/ + // XXXXXX.XML XXXXXX is clip name + // XXXXXX.XMP + // VIDEO/ + // XXXXXX.MXF + // AUDIO/ + // XXXXXXNN.MXF NN is a counter which can go from 00 to 15. + // ICON/ + // XXXXXX.BMP + // VOICE/ + // XXXXXXNN.WAV NN is a counter which can go from 00 to 99. + // PROXY/ + // XXXXXX.MP4 + // XXXXXX.BIN + + XMP_VarString contentsPath = this->rootPath + kDirChar + "CONTENTS" + kDirChar; + XMP_VarString path; + + //Add RootPath + path = this->rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + + std::string clipPathNoExt = contentsPath + "CLIP" + kDirChar + this->clipName; + // Get the files present inside CLIP folder. + path = clipPathNoExt + ".XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + path = clipPathNoExt + ".XMP"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + + // Get the files present inside VIDEO folder. + path = contentsPath + "VIDEO" + kDirChar + this->clipName + ".MXF"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + + // Get the files present inside AUDIO folder. + path = contentsPath + "AUDIO" + kDirChar; + XMP_VarString regExp; + regExp = "^" + this->clipName + "\\d\\d.MXF$"; + IOUtils::GetMatchingChildren ( *resourceList, path, regExp, false, true, true ); + + // Get the files present inside ICON folder. + path = contentsPath + "ICON" + kDirChar + this->clipName + ".BMP"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + + // Get the files present inside VOICE folder. + path = contentsPath + "VOICE" + kDirChar; + regExp = "^" + clipName + "\\d\\d.WAV$"; + IOUtils::GetMatchingChildren ( *resourceList, path, regExp, false, true, true ); + + // Get the files present inside PROXY folder. + std::string proxyPathNoExt = contentsPath + "PROXY" + kDirChar + this->clipName; + + path = proxyPathNoExt + ".MP4"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + path = proxyPathNoExt + ".BIN"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); +} // P2_MetaHandler::FillAssociatedResources + +// ================================================================================================= // P2_MetaHandler::CacheFileData // ============================= @@ -1094,22 +1210,23 @@ 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 ); + XMP_Throw ( "P2 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. + if ( ! Host_IO::Exists ( xmpPath.c_str() ) ) return; // No XMP. - // Read the entire .XMP file. + // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 + // only if the file does not exist. 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. + if ( xmpFile == 0 ) XMP_Throw ( "P2 XMP file open failure", kXMPErr_InternalFailure ); this->parent->ioRef = xmpFile; XMP_Int64 xmpLen = xmpFile->Length(); @@ -1289,6 +1406,23 @@ void P2_MetaHandler::ProcessXMP() } // P2_MetaHandler::ProcessXMP +// This function adds a dummy attribute to the clipContent/clipMetadata (whichever is non-null) +// with empty value and namespace as xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance +static XML_Node* AddXSINamespace(XML_Node *clipContent, XML_Node *clipMetadata){ + + XML_Node *parent = clipContent ? clipContent : clipMetadata; + if(parent){ + XML_Node *attrWithXSINamespace = new XML_Node ( parent, "xsi:", kCDataNode ); + attrWithXSINamespace->value=""; + attrWithXSINamespace->ns="http://www.w3.org/2001/XMLSchema-instance"; + parent->attrs.push_back(attrWithXSINamespace); + return parent; + } + + return NULL; + +} + // ================================================================================================= // P2_MetaHandler::UpdateFile // ========================== @@ -1318,7 +1452,7 @@ void P2_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( xmpFound ) { - xmlNode = this->ForceChildElement ( this->clipContent, "ClipName", 3 ); + xmlNode = this->ForceChildElement ( this->clipContent, "ClipName", 3, false ); if ( xmpValue != xmlNode->GetLeafContentValue() ) { xmlNode->SetLeafContentValue ( xmpValue.c_str() ); @@ -1330,8 +1464,10 @@ void P2_MetaHandler::UpdateFile ( bool doSafeUpdate ) 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 ); + xmlNode = this->ForceChildElement ( this->clipMetadata, "Access", 3, false ); + + // "Creator" must be first child of "Access" node else Panasonic P2 Viewer gives an error. + xmlNode = this->ForceChildElement ( xmlNode, "Creator", 4 , true); if ( xmpValue != xmlNode->GetLeafContentValue() ) { xmlNode->SetLeafContentValue ( xmpValue.c_str() ); updateLegacyXML = true; @@ -1370,7 +1506,20 @@ void P2_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( updateLegacyXML ) { std::string legacyXML, xmlPath; + + /*bug # 3217688: xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance namespace must be defined at the + root node "P2Main" in legacy XML else Panasonic P2 Viewer gives an error. So we are adding a + dummy attribute with this namespace to clipContent/clipMetadata (whichever is non-null) before + serializing the XML tree. We are also undoing it below after serialization.*/ + + XML_Node *parentNode = AddXSINamespace(this->clipContent, this->clipMetadata); this->expat->tree.Serialize ( &legacyXML ); + if(parentNode){ + // Remove the dummy attribute added to clipContent/clipMetadata. + delete parentNode->attrs[parentNode->attrs.size()-1]; + parentNode->attrs.pop_back(); + } + this->MakeClipFilePath ( &xmlPath, ".XML" ); bool haveXML = Host_IO::Exists ( xmlPath.c_str() ); diff --git a/XMPFiles/source/FileHandlers/P2_Handler.hpp b/XMPFiles/source/FileHandlers/P2_Handler.hpp index 93b2672..513e9ea 100644 --- a/XMPFiles/source/FileHandlers/P2_Handler.hpp +++ b/XMPFiles/source/FileHandlers/P2_Handler.hpp @@ -46,7 +46,6 @@ static const XMP_OptionBits kP2_HandlerFlags = (kXMPFiles_CanInjectXMP | kXMPFiles_ReturnsRawPacket | kXMPFiles_HandlerOwnsFile | kXMPFiles_AllowsSafeUpdate | - kXMPFiles_UsesSidecarXMP | kXMPFiles_FolderBasedFormat); class P2_MetaHandler : public XMPFileHandler @@ -54,6 +53,9 @@ class P2_MetaHandler : public XMPFileHandler public: bool GetFileModDate ( XMP_DateTime * modDate ); + void FillMetadataFiles ( std::vector<std::string> * metadataFiles ); + void FillAssociatedResources ( std::vector<std::string> * resourceList ); + bool IsMetadataWritable ( ); void CacheFileData(); void ProcessXMP(); @@ -95,7 +97,7 @@ private: 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 ); + XML_Node * ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, XMP_Int32 indent, XMP_Bool insertAtFront ); std::string rootPath, clipName, p2NS; diff --git a/XMPFiles/source/FileHandlers/PNG_Handler.cpp b/XMPFiles/source/FileHandlers/PNG_Handler.cpp index 52b65ff..8c2fe8c 100644 --- a/XMPFiles/source/FileHandlers/PNG_Handler.cpp +++ b/XMPFiles/source/FileHandlers/PNG_Handler.cpp @@ -47,12 +47,12 @@ bool PNG_CheckFormat ( XMP_FileFormat format, IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent); XMP_Assert ( format == kXMP_PNGFile ); - IOBuffer ioBuf; + if ( fileRef->Length() < PNG_SIGNATURE_LEN ) return false; + XMP_Uns8 buffer [PNG_SIGNATURE_LEN]; 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; + fileRef->Read ( buffer, PNG_SIGNATURE_LEN ); + if ( ! CheckBytes ( buffer, PNG_SIGNATURE_DATA, PNG_SIGNATURE_LEN ) ) return false; return true; diff --git a/XMPFiles/source/FileHandlers/PSD_Handler.cpp b/XMPFiles/source/FileHandlers/PSD_Handler.cpp index d57f91c..89e1cc6 100644 --- a/XMPFiles/source/FileHandlers/PSD_Handler.cpp +++ b/XMPFiles/source/FileHandlers/PSD_Handler.cpp @@ -48,20 +48,19 @@ using namespace std; bool PSD_CheckFormat ( XMP_FileFormat format, XMP_StringPtr filePath, - XMP_IO* fileRef, + 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 ( fileRef->Length() < 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 ); + XMP_Uns8 buffer [4]; + fileRef->ReadAll ( buffer, 4 ); + if ( ! CheckBytes ( buffer, "8BPS", 4 ) ) return false; + XMP_Uns16 version = XIO::ReadUns16_BE ( fileRef ); if ( (version != 1) && (version != 2) ) return false; return true; @@ -128,7 +127,7 @@ void PSD_MetaHandler::CacheFileData() } XMP_Uns8 psdHeader[30]; - XMP_Uns32 ioLen, cmLen, psirLen; + XMP_Uns32 ioLen, cmLen; XMP_Int64 filePos = 0; fileRef->Rewind ( ); @@ -146,10 +145,8 @@ void PSD_MetaHandler::CacheFileData() 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] ); + if ( ! XIO::CheckFileSpace ( fileRef, 4 ) ) return; // Throw? + XMP_Uns32 psirLen = XIO::ReadUns32_BE ( fileRef ); this->psirMgr.ParseFileResources ( fileRef, psirLen ); @@ -197,6 +194,8 @@ void PSD_MetaHandler::ProcessXMP() this->iptcMgr = new IPTC_Writer(); // ! Parse it later. this->exifMgr = new TIFF_FileWriter(); } + if ( this->parent ) + exifMgr->SetErrorCallback( &this->parent->errorCallback ); PSIR_Manager & psir = this->psirMgr; // Give the compiler help in recognizing non-aliases. IPTC_Manager & iptc = *this->iptcMgr; @@ -242,14 +241,8 @@ void PSD_MetaHandler::ProcessXMP() 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. - } + } catch ( ... ) { /* Ignore parsing failures, someday we hope to get partial XMP back. */ } + haveXMP = true; } // Process the legacy metadata. @@ -298,6 +291,7 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength)); if ( this->psirMgr.IsLegacyChanged() ) doInPlace = false; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; if ( doInPlace ) { @@ -315,10 +309,10 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) 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() ); + if ( progressTracker != 0 ) progressTracker->BeginWork ( this->xmpPacket.size() ); liveFile->Seek ( oldPacketOffset, kXMP_SeekFromStart ); liveFile->Write ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + if ( progressTracker != 0 ) progressTracker->WorkComplete(); } else { @@ -363,6 +357,7 @@ void PSD_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; XMP_Uns64 sourceLen = origRef->Length(); if ( sourceLen == 0 ) return; // Tolerate empty files. @@ -382,35 +377,50 @@ void PSD_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) FillPacketInfo ( this->xmpPacket, &this->packetInfo ); this->psirMgr.SetImgRsrc ( kPSIR_XMP, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + + // Calculate the total writes I/O to be done by this method. This includes header section, color + // mode section and tail length after the image resources section. The write I/O for image + // resources section is added to total work in PSIR_FileWriter::UpdateFileResources. + + origRef->Seek ( 26, kXMP_SeekFromStart ); //move to the point after Header 26 is the header length + + XMP_Uns32 cmLen,cmLen1; + origRef->Read ( &cmLen, 4 ); // get the length of color mode section + cmLen1 = GetUns32BE ( &cmLen ); + origRef->Seek ( cmLen1, kXMP_SeekFromCurrent ); //move to the end of color mode section + + XMP_Uns32 irLen; + origRef->Read ( &irLen, 4 ); // Get the source image resource section length. + irLen = GetUns32BE ( &irLen ); + + XMP_Uns64 tailOffset = 26 + 4 + cmLen1 + 4 + irLen; + XMP_Uns64 tailLength = sourceLen - tailOffset; + + // Add work for 26 bytes header, 4 bytes color mode section length, color mode section length + // and tail length after the image resources section length. + + if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)(26.0f + 4.0f + cmLen1 + tailLength) ); // 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 ); + origRef->Seek ( 4, kXMP_SeekFromCurrent ); 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 ); + XIO::Copy ( origRef, tempRef, cmLen1 ); // Copy the color mode section contents. - XMP_Uns64 tailOffset = 26 + 4 + cmLen + 4 + irLen; - XMP_Uns64 tailLength = sourceLen - tailOffset; + this->psirMgr.UpdateFileResources ( origRef, tempRef, abortProc, abortArg ,progressTracker ); origRef->Seek ( tailOffset, kXMP_SeekFromStart ); tempRef->Seek ( 0, kXMP_SeekFromEnd ); XIO::Copy ( origRef, tempRef, tailLength ); // Copy the tail of the file. this->needsUpdate = false; + if ( progressTracker != 0 ) progressTracker->WorkComplete(); } // PSD_MetaHandler::WriteTempFile diff --git a/XMPFiles/source/FileHandlers/PSD_Handler.hpp b/XMPFiles/source/FileHandlers/PSD_Handler.hpp index a64b6df..51dd4b9 100644 --- a/XMPFiles/source/FileHandlers/PSD_Handler.hpp +++ b/XMPFiles/source/FileHandlers/PSD_Handler.hpp @@ -38,7 +38,8 @@ static const XMP_OptionBits kPSD_HandlerFlags = (kXMPFiles_CanInjectXMP | kXMPFiles_CanReconcile | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | - kXMPFiles_AllowsSafeUpdate); + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress); class PSD_MetaHandler : public XMPFileHandler { diff --git a/XMPFiles/source/FileHandlers/PostScript_Handler.cpp b/XMPFiles/source/FileHandlers/PostScript_Handler.cpp index bd5d4f7..d21ab8d 100644 --- a/XMPFiles/source/FileHandlers/PostScript_Handler.cpp +++ b/XMPFiles/source/FileHandlers/PostScript_Handler.cpp @@ -21,19 +21,15 @@ #include "XMPFiles/source/FormatSupport/XMPScanner.hpp" #include "XMPFiles/source/FileHandlers/Scanner_Handler.hpp" -using namespace std; +#include <algorithm> +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 // ========================== @@ -58,107 +54,7 @@ bool PostScript_CheckFormat ( XMP_FileFormat format, 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; + return PostScript_Support::IsValidPSFile(fileRef,format) ; } // PostScript_CheckFormat @@ -166,7 +62,8 @@ bool PostScript_CheckFormat ( XMP_FileFormat format, // PostScript_MetaHandler::PostScript_MetaHandler // ============================================== -PostScript_MetaHandler::PostScript_MetaHandler ( XMPFiles * _parent ) +PostScript_MetaHandler::PostScript_MetaHandler ( XMPFiles * _parent ):dscFlags(0),docInfoFlags(0) + ,containsXMPHint(false),fileformat(kXMP_UnknownFile) { this->parent = _parent; this->handlerFlags = kPostScript_HandlerFlags; @@ -192,12 +89,6 @@ PostScript_MetaHandler::~PostScript_MetaHandler() // 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; @@ -214,9 +105,9 @@ int PostScript_MetaHandler::FindPostScriptHint() fileRef->Rewind(); if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; - XMP_Uns32 temp1 = GetUns32BE ( ioBuf.ptr ); + XMP_Uns32 fileheader = GetUns32BE ( ioBuf.ptr ); - if ( temp1 == 0xC5D0D3C6 ) { + if ( fileheader == 0xC5D0D3C6 ) { if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return false; @@ -235,14 +126,14 @@ int PostScript_MetaHandler::FindPostScriptHint() XMP_Throw ( "PostScript_MetaHandler::FindPostScriptHint - User abort", kXMPErr_UserAbort ); } - if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsXMPLength ) ) return kPSHint_NoMarker; + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsXMPString.length() ) ) return kPSHint_NoMarker; - if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString), kPSEndCommentLength ) ) { + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() ) ) { // Found "%%EndComments", don't look any further. return kPSHint_NoMarker; - } else if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsXMPString), kPSContainsXMPLength ) ) { + } else if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsXMPString.c_str()), kPSContainsXMPString.length() ) ) { // Not "%%EndComments" or "%ADO_ContainsXMP:", skip past the end of this line. do { @@ -255,7 +146,7 @@ int PostScript_MetaHandler::FindPostScriptHint() // Found "%ADO_ContainsXMP:", look for the main packet location option. - ioBuf.ptr += kPSContainsXMPLength; + ioBuf.ptr += kPSContainsXMPString.length(); 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; @@ -357,35 +248,51 @@ bool PostScript_MetaHandler::FindFirstPacket() bufLen = 0; fileRef->Rewind(); // Seek back to the beginning of the file. + bool firstfound=false; - while ( true ) { + while ( true ) + { - if ( checkAbort && abortProc(abortArg) ) { + 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. + if ( bufLen == 0 ) return firstfound; // 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; + for ( int i = 0; i < snipCount; ++i ) + { + if ( snips[i].fState == XMPScanner::eValidPacketSnip ) + { + if (!firstfound) + { + 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'); + firstPacketInfo=packetInfo; + lastPacketInfo=packetInfo; + firstfound=true; + } + else + { + lastPacketInfo.offset = snips[i].fOffset; + lastPacketInfo.length = (XMP_Int32)snips[i].fLength; + lastPacketInfo.charForm = snips[i].fCharForm; + lastPacketInfo.writeable = (snips[i].fAccess == 'w'); + } } } } - - return false; + + return firstfound; } // FindFirstPacket @@ -398,14 +305,8 @@ bool PostScript_MetaHandler::FindFirstPacket() // 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; @@ -426,8 +327,10 @@ bool PostScript_MetaHandler::FindLastPacket() fileRef->Rewind(); // Seek back to the beginning of the file. - for ( bufPos = 0; bufPos < (size_t)fileLen; bufPos += bufLen ) { - if ( checkAbort && abortProc(abortArg) ) { + 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 ); @@ -443,132 +346,1495 @@ bool PostScript_MetaHandler::FindLastPacket() XMPScanner::SnipInfoVector snips ( snipCount ); scanner.Report ( snips ); - for ( pkt = snipCount-1; pkt >= 0; --pkt ) { - if ( snips[pkt].fState == XMPScanner::eValidPacketSnip ) break; + bool lastfound=false; + for ( int i = 0; i < snipCount; ++i ) + { + if ( snips[i].fState == XMPScanner::eValidPacketSnip ) + { + if (!lastfound) + { + 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'); + firstPacketInfo=packetInfo; + lastPacketInfo=packetInfo; + lastfound=true; + } + else + { + lastPacketInfo.offset = snips[i].fOffset; + lastPacketInfo.length = (XMP_Int32)snips[i].fLength; + lastPacketInfo.charForm = snips[i].fCharForm; + lastPacketInfo.writeable = (snips[i].fAccess == 'w'); + packetInfo=lastPacketInfo; + } + } } + return lastfound; - 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; +} // PostScript_MetaHandler::FindLastPacket + +// ================================================================================================= +// PostScript_MetaHandler::setTokenInfo +// ==================================== +// +// Function Sets the docInfo flag for tokens(PostScript DSC comments/ Docinfo Dictionary values) +// when parsing the file stream.Also takes note of the token offset from the start of the file +// and the length of the token +void PostScript_MetaHandler::setTokenInfo(TokenFlag tFlag,XMP_Int64 offset,XMP_Int64 length) +{ + if (!(docInfoFlags&tFlag)&&tFlag>=kPS_ADOContainsXMP && tFlag<=kPS_EndPostScript) + { + size_t index=0; + XMP_Uns64 flag=tFlag; + while(flag>>=1) index++; + fileTokenInfo[index].offsetStart=offset; + fileTokenInfo[index].tokenlen=length; + docInfoFlags|=tFlag; } +} - return false; +// ================================================================================================= +// PostScript_MetaHandler::getTokenInfo +// ==================================== +// +// Function returns the token info for the flag, which was collected in parsing the input file +// +PostScript_MetaHandler::TokenLocation& PostScript_MetaHandler::getTokenInfo(TokenFlag tFlag) +{ + if ((docInfoFlags&tFlag)&&tFlag>=kPS_ADOContainsXMP && tFlag<=kPS_EndPostScript) + { + size_t index=0; + XMP_Uns64 flag=tFlag; + while(flag>>=1) index++; + return fileTokenInfo[index]; + } + return fileTokenInfo[kPS_NoData]; +} -} // PostScript_MetaHandler::FindLastPacket +// ================================================================================================= +// PostScript_MetaHandler::ExtractDSCCommentValue +// ============================================== +// +// Function extracts the DSC comment value when parsing the file.This may be later used to reconcile +// +bool PostScript_MetaHandler::ExtractDSCCommentValue(IOBuffer &ioBuf,NativeMetadataIndex index) +{ + + XMP_IO* fileRef = this->parent->ioRef; + if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false; + if ( !IsNewline ( *ioBuf.ptr ) ) + { + do + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + nativeMeta[index] += *ioBuf.ptr; + ++ioBuf.ptr; + } while ( ! IsNewline ( *ioBuf.ptr) ); + if (!PostScript_Support::HasCodesGT127(nativeMeta[index])) + { + dscFlags|=nativeIndextoFlag[index]; + } + else + nativeMeta[index].clear(); + } + return true; +} -#else -bool PostScript_MetaHandler::FindLastPacket() +// ================================================================================================= +// PostScript_MetaHandler::ExtractContainsXMPHint +// ============================================== +// +// Function extracts the the value of "ADOContainsXMP:" DSC comment's value +// +bool PostScript_MetaHandler::ExtractContainsXMPHint(IOBuffer &ioBuf,XMP_Int64 containsXMPStartpos) { - int err, snipCount; - bool found = false; - XMP_Int64 backPos, backLen; - size_t ioCount; + XMP_IO* fileRef = this->parent->ioRef; + int xmpHint = kPSHint_NoMain; // ! From here on, a failure means "no main", not "no marker". + //checkfor atleast one whitespace + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) return false; + //skip extra ones + if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false; + if ( IsNewline ( *ioBuf.ptr ) ) return false; // Reached the end of the ContainsXMP comment. + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 6 ) ) return false ; + + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("NoMain"), 6 ) ) + { + ioBuf.ptr += 6; + if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false; + if ( ! IsNewline( *ioBuf.ptr) ) return false; + this->psHint = kPSHint_NoMain; + setTokenInfo(kPS_ADOContainsXMP,containsXMPStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-containsXMPStartpos); + + } + else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainFi"), 6 ) ) + { + ioBuf.ptr += 6; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return false; + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("rst"), 3 ) ) + { + ioBuf.ptr += 3; + if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false; + if ( ! IsNewline( *ioBuf.ptr) ) return false; + this->psHint = kPSHint_MainFirst; + setTokenInfo(kPS_ADOContainsXMP,containsXMPStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-containsXMPStartpos); + containsXMPHint=true; + } + } + else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainLa"), 6 ) ) + { + ioBuf.ptr += 6; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return false; + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("st"), 2 ) ) { + ioBuf.ptr += 2; + if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false; + if ( ! IsNewline( *ioBuf.ptr) ) return false; + this->psHint = kPSHint_MainLast; + setTokenInfo(kPS_ADOContainsXMP,containsXMPStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-containsXMPStartpos); + containsXMPHint=true; + } + } + else + { + if ( ! PostScript_Support::SkipUntilNewline(fileRef,ioBuf) ) return false; + } + return true; +} - XMP_IO* fileRef = this->parent->fileRef; - XMP_Int64 fileLen = fileRef->Length(); - XMP_PacketInfo & packetInfo = this->packetInfo; - XMPScanner scanner ( fileLen ); - XMPScanner::SnipInfoVector snips; +// ================================================================================================= +// PostScript_MetaHandler::ExtractDocInfoDict +// ============================================== +// +// Function extracts the the value of DocInfo Dictionary.The keys that are looked in the dictionary +// are Creator, CreationDate, ModDate, Author, Title, Subject and Keywords.Other keys for the +// Dictionary are ignored +bool PostScript_MetaHandler::ExtractDocInfoDict(IOBuffer &ioBuf) +{ + XMP_Uns8 ch; + XMP_IO* fileRef = this->parent->ioRef; + XMP_Int64 endofDocInfo=(ioBuf.ptr-ioBuf.data)+ioBuf.filePos; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if ( IsWhitespace (*ioBuf.ptr)) + { + //skip the whitespaces + if ( ! ( PostScript_Support::SkipTabsAndSpaces(fileRef, ioBuf))) return false; + //check the pdfmark + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsPdfmarkString.length() ) ) return false; + if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsPdfmarkString.c_str()), kPSContainsPdfmarkString.length() ) ) return false; + //reverse the direction to collect data + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + ch=*ioBuf.ptr; + --ioBuf.ptr; + if (ch=='/') break;//slash of /DOCINFO + } + //skip white spaces + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (!IsWhitespace(*ioBuf.ptr)) break; + --ioBuf.ptr; + } + + bool findingkey=false; + std::string key, value; + while(true) + { + XMP_Uns32 noOfMarks=0; + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (*ioBuf.ptr==')') + { + --ioBuf.ptr; + while(true) + { + //get the string till '(' + if (*ioBuf.ptr=='(') + { + if(findingkey) + { + reverse(key.begin(), key.end()); + reverse(value.begin(), value.end()); + RegisterKeyValue(key,value); + } + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + --ioBuf.ptr; + findingkey=!findingkey; + break; + } + else + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (findingkey) + key+=*ioBuf.ptr; + else + value+=*ioBuf.ptr; + --ioBuf.ptr; + } + } + } + else if(*ioBuf.ptr=='[') + { + //end of Doc Info parsing + //return; + break; + } + else + { + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (findingkey) + key+=*ioBuf.ptr; + else + value+=*ioBuf.ptr; + --ioBuf.ptr; + if (*ioBuf.ptr=='/') + { + if(findingkey) + { + reverse(key.begin(), key.end()); + reverse(value.begin(), value.end()); + RegisterKeyValue(key,value); + } + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + --ioBuf.ptr; + findingkey=!findingkey; + break; + } + else if(IsWhitespace(*ioBuf.ptr)) + { + //something not expected in Doc Info + break; + } + } + } + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (!IsWhitespace(*ioBuf.ptr)) break; + --ioBuf.ptr; + } + + } + + fileRef->Rewind(); + FillBuffer (fileRef, endofDocInfo, &ioBuf ); + return true; + }//white space not after DOCINFO + return false; +} - enum { kBufferSize = 64*1024 }; - XMP_Uns8 buffer [kBufferSize]; +// ================================================================================================= +// PostScript_MetaHandler::ParsePSFile +// =================================== +// +// Main parser for the Post script file.This is where all the DSC comments , Docinfo key value pairs +// and other insertion related Data is looked for and stored +void PostScript_MetaHandler::ParsePSFile() +{ + bool found = false; + IOBuffer ioBuf; + + XMP_IO* fileRef = this->parent->ioRef; XMP_AbortProc abortProc = this->parent->abortProc; - void * abortArg = this->parent->abortArg; - const bool checkAbort = (abortProc != 0); + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); - backPos = fileLen; - backLen = 0; + //Determine the file type PS or EPS + if ( ! PostScript_Support::IsValidPSFile(fileRef,this->fileformat) ) return ; + // Check for the binary EPSF preview header. - while ( true ) { + fileRef->Rewind(); + if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return ; + XMP_Uns32 fileheader = GetUns32BE ( ioBuf.ptr ); + if ( fileheader == 0xC5D0D3C6 ) + { + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return ; + + XMP_Uns32 psOffset = GetUns32LE ( ioBuf.ptr+4 ); // PostScript offset. + XMP_Uns32 psLength = GetUns32LE ( ioBuf.ptr+8 ); // PostScript length. + + setTokenInfo(kPS_EndPostScript,psOffset+psLength,0); + MoveToOffset ( fileRef, psOffset, &ioBuf ); + + } + + while ( true ) + { if ( checkAbort && abortProc(abortArg) ) { - XMP_Throw ( "PostScript_MetaHandler::FindLastPacket - User abort", kXMPErr_UserAbort ); + XMP_Throw ( "PostScript_MetaHandler::FindPostScriptHint - User abort", kXMPErr_UserAbort ); } - backLen = kBufferSize; - if ( backPos < kBufferSize ) backLen = backPos; - if ( backLen == 0 ) return false; // Must be at BoF, no packets found. + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsForString.length() ) ) return ; - backPos -= backLen; - fileRef->Seek ( backPos, kXMP_SeekFromStart ); // Seek back to the start of the next buffer. + if ( (CheckFileSpace ( fileRef, &ioBuf, kPSEndCommentString.length() )&& + CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() ) + )|| *ioBuf.ptr!='%' || !(*(ioBuf.ptr+1)>32 && *(ioBuf.ptr+1)<=126 )) // implicit endcomment check + { + if (CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() )) + { + setTokenInfo(kPS_EndComments,ioBuf.filePos+ioBuf.ptr-ioBuf.data,kPSEndCommentString.length()); + ioBuf.ptr+=kPSEndCommentString.length(); + } + else + { + setTokenInfo(kPS_EndComments,ioBuf.filePos+ioBuf.ptr-ioBuf.data,0); + } + // Found "%%EndComments", look for docInfo Dictionary + // skip past the end of this line. + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ; + if (! IsWhitespace (*ioBuf.ptr)) break; + ++ioBuf.ptr; + } + // search for /DOCINFO + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 5 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("/DOCI"), 5 ) + && CheckFileSpace ( fileRef, &ioBuf, kPSContainsDocInfoString.length() ) + &&CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsDocInfoString.c_str()), kPSContainsDocInfoString.length() )) + + { + + ioBuf.ptr+=kPSContainsDocInfoString.length(); + ExtractDocInfoDict(ioBuf); + }// DOCINFO Not found in document + else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%Beg"), 5 )) + {//possibly one of %%BeginProlog %%BeginSetup %%BeginBinary %%BeginData + // %%BeginDocument %%BeginPageSetup + XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data; + ioBuf.ptr+=5; + if (!CheckFileSpace ( fileRef, &ioBuf, 6 )) return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("inProl"), 6 )) + {//%%BeginProlog + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 2 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("og"), 2 )) + { + ioBuf.ptr+=2; + setTokenInfo(kPS_BeginProlog,begStartpos,13); + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inSetu"), 6 )) + {//%%BeginSetup + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 1 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("p"), 1 )) + { + ioBuf.ptr+=1; + setTokenInfo(kPS_BeginSetup,begStartpos,12); + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inBina"), 6 )) + {//%%BeginBinary + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 3 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("ry"), 3 )) + { + ioBuf.ptr+=3; + //ignore till %%EndBinary + while(true) + { + if (!CheckFileSpace ( fileRef, &ioBuf, 12 ))return; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EndBinary"), 11 )) + { + ioBuf.ptr+=11; + if (IsWhitespace(*ioBuf.ptr)) + { + ioBuf.ptr++; + break; + } + } + ++ioBuf.ptr; + } + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inData"), 6 )) + {//%%BeginData + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 1 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr(":"), 1 )) + { + //ignore till %%EndData + while(true) + { + if (!CheckFileSpace ( fileRef, &ioBuf, 10 ))return; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EndData"), 9 )) + { + ioBuf.ptr+=9; + if (IsWhitespace(*ioBuf.ptr)) + { + ioBuf.ptr++; + break; + } + } + ++ioBuf.ptr; + } + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inDocu"), 6 )) + {// %%BeginDocument + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 5 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("ment:"), 5 )) + { + ioBuf.ptr+=5; + //ignore till %%EndDocument + while(true) + { + if (!CheckFileSpace ( fileRef, &ioBuf, 14 ))return; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EndDocument"), 13 )) + { + ioBuf.ptr+=13; + if (IsWhitespace(*ioBuf.ptr)) + { + ioBuf.ptr++; + break; + } + } + ++ioBuf.ptr; + } + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inPage"), 6 )) + {// %%BeginPageSetup + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 5 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("Setup"), 5 )) + { + ioBuf.ptr+=5; + setTokenInfo(kPS_BeginPageSetup,begStartpos,16); + } + } + } + else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%End"), 5 )) + {//possibly %%EndProlog %%EndSetup %%EndPageSetup %%EndPageComments + XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data; + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 5 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("Prolo"), 5 )) + {// %%EndProlog + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("g"), 1 )) + { + ioBuf.ptr+=1; + setTokenInfo(kPS_EndProlog,begStartpos,11); + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("Setup"), 5 )) + {//%%EndSetup + ioBuf.ptr+=5; + setTokenInfo(kPS_EndSetup,begStartpos,10); + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("PageS"), 5 )) + {//%%EndPageSetup + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("etup"), 4 )) + { + ioBuf.ptr+=4; + setTokenInfo(kPS_EndPageSetup,begStartpos,14); + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("PageC"), 5 )) + {//%%EndPageComments + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 7 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("omments"), 7 )) + { + ioBuf.ptr+=7; + setTokenInfo(kPS_EndPageComments,begStartpos,17); + } + } + } + else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%Pag"), 5 )) + { + XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data; + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr(":"), 2 )) + { + ioBuf.ptr+=2; + while(!IsNewline(*ioBuf.ptr)) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ; + ++ioBuf.ptr; + } + setTokenInfo(kPS_Page,begStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-begStartpos); + } - #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 ); + } + else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%Tra"), 5 )) + { + XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data; + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("iler"), 4 )) + { + ioBuf.ptr+=4; + while(!IsNewline(*ioBuf.ptr)) ++ioBuf.ptr; + setTokenInfo(kPS_Trailer,begStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-begStartpos); + } + } + else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EOF"), 5 )) + { + ioBuf.ptr+=5; + setTokenInfo(kPS_EOF,ioBuf.filePos+ioBuf.ptr-ioBuf.data,5); + } + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ; + ++ioBuf.ptr; + } + //dont have to search after this DOCINFO last thing + return; + + }else if (!(kPS_Creator & dscFlags) && + CheckFileSpace ( fileRef, &ioBuf, kPSContainsForString.length() )&& + CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsForString.c_str()), kPSContainsForString.length() )) + { + ioBuf.ptr+=kPSContainsForString.length(); + if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscFor) ) return ; + } + else if (!(kPS_CreatorTool & dscFlags) && + CheckFileSpace ( fileRef, &ioBuf, kPSContainsCreatorString.length() )&& + CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsCreatorString.c_str()), kPSContainsCreatorString.length() )) + { + ioBuf.ptr+=kPSContainsCreatorString.length(); + if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscCreator) ) return ; + } + else if (!(kPS_CreateDate & dscFlags) && + CheckFileSpace ( fileRef, &ioBuf, kPSContainsCreateDateString.length() )&& + CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsCreateDateString.c_str()), kPSContainsCreateDateString.length() )) + { + + ioBuf.ptr+=kPSContainsCreateDateString.length(); + if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscCreateDate) ) return ; + } + else if (!(kPS_Title & dscFlags) && + CheckFileSpace ( fileRef, &ioBuf, kPSContainsTitleString.length() )&& + CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsTitleString.c_str()), kPSContainsTitleString.length() )) + { + + ioBuf.ptr+=kPSContainsTitleString.length(); + if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscTitle) ) return ; + } + else if( CheckFileSpace ( fileRef, &ioBuf, kPSContainsXMPString.length() )&& + ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsXMPString.c_str()), kPSContainsXMPString.length() ) )) { - scanner.Scan ( buffer, backPos, backLen ); - snipCount = scanner.GetSnipCount(); - scanner.Report ( snips ); + // Found "%ADO_ContainsXMP:", look for the main packet location option. + + XMP_Int64 containsXMPStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data; + ioBuf.ptr += kPSContainsXMPString.length(); + ExtractContainsXMPHint(ioBuf,containsXMPStartpos); - for ( int i = snipCount-1; i >= 0; --i ) { + } // Found "%ADO_ContainsXMP:". + //Some other DSC comments skip past the end of this line. + if ( ! PostScript_Support::SkipUntilNewline(fileRef,ioBuf) ) return ; - if ( snips[i].fState == XMPScanner::eValidPacketSnip ) { + } // Outer marker loop. - return VerifyMainPacket ( fileRef, snips[i].fOffset, snips[i].fLength, format, beLenient, mainInfo ); + return ; // Should never reach here. - } 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. +// ================================================================================================= +// PostScript_MetaHandler::ReadXMPPacket +// ===================================== +// +// Helper method read the raw xmp into a string from a file +void PostScript_MetaHandler::ReadXMPPacket (std::string & xmpPacket ) +{ + if ( packetInfo.length == 0 ) XMP_Throw ( "ReadXMPPacket - No XMP packet", kXMPErr_BadXMP ); - size_t fwdPos = snips[i].fOffset + snips[i].fLength; - fileRef->Seek ( fwdPos, kXMP_SeekFromStart ); // Seek to the end of the partial snip. + xmpPacket.erase(); + xmpPacket.reserve ( packetInfo.length ); + xmpPacket.append ( packetInfo.length, ' ' ); - 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; - } + XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() ); // Don't set until after reserving the space! - 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; - } + this->parent->ioRef->Seek ( packetInfo.offset, kXMP_SeekFromStart ); + this->parent->ioRef->ReadAll ( (char*)packetStr, packetInfo.length ); + +} // ReadXMPPacket + +// ================================================================================================= +// PostScript_MetaHandler::RegisterKeyValue +// ========================================= +// +// Helper method registers the Key value pairs for the DocInfo dictionary and sets the Appriopriate +// DocInfo flags +void PostScript_MetaHandler::RegisterKeyValue(std::string& key, std::string& value) +{ + size_t vallen=value.length(); + if (key.length()==0||vallen==0) + { + key.clear(); + value.clear(); + return; + } + for (size_t index=0;index<vallen;index++) + { + if ((unsigned char)value[index]>127) + { + key.clear(); + value.clear(); + return; + } + } + switch (key[0]) + { + case 'A': // probably Author + { + if (!key.compare("Author")) + { + nativeMeta[kPS_docInfoAuthor]=value; + docInfoFlags|=kPS_Creator; + } + break; + } + case 'C': //probably Creator or CreationDate + { + if (!key.compare("Creator")) + { + nativeMeta[kPS_docInfoCreator]=value; + docInfoFlags|=kPS_CreatorTool; + } + else if (!key.compare("CreationDate")) + { + nativeMeta[kPS_docInfoCreateDate]=value; + docInfoFlags|=kPS_CreateDate; + } + break; + } + case 'T': // probably Title + { + if (!key.compare("Title")) + { + nativeMeta[kPS_docInfoTitle]=value; + docInfoFlags|=kPS_Title; + } + break; + } + case 'S':// probably Subject + { + if (!key.compare("Subject")) + { + nativeMeta[kPS_docInfoSubject]=value; + docInfoFlags|=kPS_Description; + } + break; + } + case 'K':// probably Keywords + { + if (!key.compare("Keywords")) + { + nativeMeta[kPS_docInfoKeywords]=value; + docInfoFlags|=kPS_Subject; + } + break; + } + case 'M': // probably ModDate + { + if (!key.compare("ModDate")) + { + nativeMeta[kPS_docInfoModDate]=value; + docInfoFlags|=kPS_ModifyDate; + } + break; + } + default: //ignore everything else + { + ; } + } + key.clear(); + value.clear(); +} - } // Backwards snip loop. - } // Backwards read loop. +// ================================================================================================= +// PostScript_MetaHandler::ReconcileXMP +// ========================================= +// +// Helper method that facilitates the read time reconcilliation of native metadata - return false; // Should never get here. +void PostScript_MetaHandler::ReconcileXMP( const std::string &xmpStr, std::string *outStr ) +{ + SXMPMeta xmp; + xmp.ParseFromBuffer( xmpStr.c_str(), xmpStr.length() ); + // Adding creator Toll if any + if (!xmp.DoesPropertyExist ( kXMP_NS_XMP,"CreatorTool" )) + { + if(docInfoFlags&kPS_CreatorTool) + { + xmp.SetProperty( kXMP_NS_XMP, "CreatorTool", nativeMeta[kPS_docInfoCreator] ); + } + else if (dscFlags&kPS_CreatorTool) + { + xmp.SetProperty( kXMP_NS_XMP, "CreatorTool", nativeMeta[kPS_dscCreator] ); + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_XMP,"CreateDate" )) + { + if(docInfoFlags&kPS_CreateDate && nativeMeta[kPS_docInfoCreateDate].length()>0) + { + std::string xmpdate=PostScript_Support::ConvertToDate(nativeMeta[kPS_docInfoCreateDate].c_str()); + if (xmpdate.length()>0) + { + xmp.SetProperty( kXMP_NS_XMP, "CreateDate", xmpdate ); + } + } + else if (dscFlags&kPS_CreateDate&& nativeMeta[kPS_dscCreateDate].length()>0) + { + std::string xmpdate=PostScript_Support::ConvertToDate(nativeMeta[kPS_dscCreateDate].c_str()); + xmp.SetProperty( kXMP_NS_XMP, "CreateDate", xmpdate ); + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_XMP,"ModifyDate" )) + { + if(docInfoFlags&kPS_ModifyDate && nativeMeta[kPS_docInfoModDate].length()>0) + { + std::string xmpdate=PostScript_Support::ConvertToDate(nativeMeta[kPS_docInfoModDate].c_str()); + if (xmpdate.length()>0) + { + xmp.SetProperty( kXMP_NS_XMP, "ModifyDate", xmpdate ); + } + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"creator" )) + { + if(docInfoFlags&kPS_Creator) + { + xmp.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, + nativeMeta[kPS_docInfoAuthor] ); + } + else if ( dscFlags&kPS_Creator) + { + xmp.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, + nativeMeta[kPS_dscFor] ); + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"title" )) + { + if(docInfoFlags&kPS_Title) + { + xmp.SetLocalizedText( kXMP_NS_DC, "title",NULL,"x-default", nativeMeta[kPS_docInfoTitle] ); + } + else if ( dscFlags&kPS_Title) + { + xmp.SetLocalizedText( kXMP_NS_DC, "title",NULL,"x-default", nativeMeta[kPS_dscTitle] ); + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"description" )) + { + if(docInfoFlags&kPS_Description) + { + xmp.SetLocalizedText( kXMP_NS_DC, "description",NULL,"x-default", nativeMeta[kPS_docInfoSubject] ); + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"subject" )) + { + if(docInfoFlags&kPS_Subject) + { + xmp.AppendArrayItem( kXMP_NS_DC, "subject", kXMP_PropArrayIsUnordered, + nativeMeta[kPS_docInfoKeywords], kXMP_NoOptions ); + } + } -} // PostScript_MetaHandler::FindLastPacket + if (packetInfo.length>0) + { + try + { + xmp.SerializeToBuffer( outStr, kXMP_ExactPacketLength|kXMP_UseCompactFormat,packetInfo.length); + } + catch(...) + { + xmp.SerializeToBuffer( outStr, kXMP_UseCompactFormat,0); + } + } + else + { + xmp.SerializeToBuffer( outStr, kXMP_UseCompactFormat,0); + } +} -#endif // ================================================================================================= // PostScript_MetaHandler::CacheFileData // ===================================== - void PostScript_MetaHandler::CacheFileData() { this->containsXMP = false; - this->psHint = FindPostScriptHint(); + this->psHint = kPSHint_NoMarker; + ParsePSFile(); if ( this->psHint == kPSHint_MainFirst ) { this->containsXMP = FindFirstPacket(); } else if ( this->psHint == kPSHint_MainLast ) { this->containsXMP = FindLastPacket(); + }else + { + //find first packet in case of NoMain or absence of hint + //When inserting new packet should be inserted in front + //any other existing packet + FindFirstPacket(); } + if ( this->containsXMP ) + { + ReadXMPPacket ( xmpPacket ); + } +} // PostScript_MetaHandler::CacheFileData + +// ================================================================================================= +// PostScript_MetaHandler::ProcessXMP +// ================================== +void PostScript_MetaHandler::ProcessXMP() +{ + + XMP_Assert ( ! this->processedXMP ); + this->processedXMP = true; // Make sure we only come through here once. - if ( this->containsXMP ) ReadXMPPacket ( this ); + std::string xmptempStr=xmpPacket; + + //Read time reconciliation with native metadata + try + { + ReconcileXMP(xmptempStr, &xmpPacket); + } + catch(...) + { + } + if ( ! this->xmpPacket.empty() ) + { + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + } + if (xmpPacket.length()>0) + { + this->containsXMP = true; // Assume we had something for the XMP. + } +} -} // PostScript_MetaHandler::CacheFileData // ================================================================================================= +// PostScript_MetaHandler::modifyHeader +// ===================================== +// +// Method modifies the header (if one is present) for the postscript offset, tiff offset etc. +// when an XMP update resulted in increase in the file size(non-inplace updates) +void PostScript_MetaHandler::modifyHeader(XMP_IO* fileRef,XMP_Int64 extrabytes,XMP_Int64 offset ) +{ + //change the header + IOBuffer temp; + fileRef->Rewind(); + if ( ! CheckFileSpace ( fileRef, &temp, 4 ) ) return ; + XMP_Uns32 fileheader = GetUns32BE ( temp.ptr ); + + if ( fileheader == 0xC5D0D3C6 ) + { + XMP_Uns8 buffLE[4]; + if ( ! CheckFileSpace ( fileRef, &temp, 32 ) ) return ; + XMP_Uns32 psLength = GetUns32LE ( temp.ptr+8 ); // PostScript length. + if (psLength>0) + { + psLength+=extrabytes; + PutUns32LE ( psLength, buffLE); + fileRef->Seek ( 8, kXMP_SeekFromStart ); + fileRef->Write(buffLE,4); + } + XMP_Uns32 wmfOffset = GetUns32LE ( temp.ptr+12 ); // WMF offset. + if (wmfOffset>0 && wmfOffset>offset) + { + wmfOffset+=extrabytes; + PutUns32LE ( wmfOffset, buffLE); + fileRef->Seek ( 12, kXMP_SeekFromStart ); + fileRef->Write(buffLE,4); + } + + XMP_Uns32 tiffOffset = GetUns32LE ( temp.ptr+20 ); // Tiff offset. + if (tiffOffset>0 && tiffOffset>offset) + { + tiffOffset+=extrabytes; + PutUns32LE ( tiffOffset, buffLE); + fileRef->Seek ( 20, kXMP_SeekFromStart ); + fileRef->Write(buffLE,4); + } + //set the checksum to 0xFFFFFFFF + XMP_Uns16 checksum=0xFFFF; + PutUns16LE ( checksum, buffLE); + fileRef->Seek ( 28, kXMP_SeekFromStart ); + fileRef->Write(buffLE,2); + } +} + +// ================================================================================================= +// PostScript_MetaHandler::DetermineUpdateMethod +// ============================================= +// +// The policy followed to update a Postscript file is +// a) if the update can fit into the existing xpacket size, go for inplace update. +// b) If sub file decode filter is used to embed the metadata expand the metadata +// and the move the rest contents(after the xpacket) by some calc offset. +// c) If some other method is used to embed the xpacket readstring or readline +// insert a new metadata xpacket before the existing xpacket. +// The preference to use these methods is in the same order a , b and then c +// DetermineUpdateMethod helps to decide which method be used for the given +// input file +// +UpdateMethod PostScript_MetaHandler::DetermineUpdateMethod(std::string & outStr) +{ + SXMPMeta xmp; + std::string & xmpPacket = this->xmpPacket; + XMP_PacketInfo & packetInfo = this->packetInfo; + xmp.ParseFromBuffer( xmpPacket.c_str(), xmpPacket.length() ); + if (packetInfo.length>0) + { + try + { + //First try to fit the modified XMP data into existing XMP packet length + //prefers Inplace + xmp.SerializeToBuffer( &outStr, kXMP_ExactPacketLength|kXMP_UseCompactFormat,packetInfo.length); + } + catch(...) + { + // if it doesnt fit :( + xmp.SerializeToBuffer( &outStr, kXMP_UseCompactFormat,0); + } + } + else + { + // this will be the case for Injecting new metadata + xmp.SerializeToBuffer( &outStr, kXMP_UseCompactFormat,0); + } + if ( this->containsXMPHint && (outStr.size() == (size_t)packetInfo.length) ) + { + return kPS_Inplace; + } + else if (this->containsXMPHint && PostScript_Support::IsSFDFilterUsed(this->parent->ioRef,packetInfo.offset)) + { + return kPS_ExpandSFDFilter; + } + else + { + return kPS_InjectNew; + } + +} + +// ================================================================================================= +// PostScript_MetaHandler::InplaceUpdate +// ===================================== +// +// Method does the inplace update of the metadata +void PostScript_MetaHandler::InplaceUpdate (std::string &outStr,XMP_IO* &tempRef ,bool doSafeUpdate) +{ + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Int64 pos = 0; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + //Inplace update of metadata + if (!doSafeUpdate) + { + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) outStr.length() ); + fileRef->Seek(packetInfo.offset,kXMP_SeekFromStart); + fileRef->Write((void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length())); + } + else + { + if ( ! tempRef ) tempRef=fileRef->DeriveTemp(); + pos=fileRef->Length(); + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) pos ); + //copy data till xmp Packet + fileRef->Seek(0,kXMP_SeekFromStart); + XIO::Copy ( fileRef, tempRef, packetInfo.offset, this->parent->abortProc, this->parent->abortArg ); + + //insert the new XMP packet + fileRef->Seek(packetInfo.offset+packetInfo.length,kXMP_SeekFromStart); + tempRef->Write((void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length())); + + //copy the rest of data + XIO::Copy ( fileRef, tempRef,pos-packetInfo.offset-packetInfo.length, this->parent->abortProc, this->parent->abortArg ); + } +} + + +// ================================================================================================= +// PostScript_MetaHandler::ExpandingSFDFilterUpdate +// ================================================ +// +// Method updates the metadata by expanding it +void PostScript_MetaHandler::ExpandingSFDFilterUpdate (std::string &outStr,XMP_IO* &tempRef ,bool doSafeUpdate) +{ + //If SubFileDecode Filter is present expanding the + //existing metadata is easy + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Int64 pos = 0; + XMP_Int32 extrapacketlength=outStr.length()-packetInfo.length; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) (extrapacketlength + fileRef->Length() -packetInfo.offset+14) ); + if (!doSafeUpdate) + { + size_t bufSize=extrapacketlength/(kIOBufferSize) +1*(extrapacketlength!=(kIOBufferSize)); + std::vector<IOBuffer> tempfilebuffer1(bufSize); + IOBuffer temp; + XMP_Int64 readpoint=packetInfo.offset+packetInfo.length,writepoint=packetInfo.offset; + fileRef->Seek ( readpoint, kXMP_SeekFromStart ); + + for(size_t x=0;x<bufSize;x++) + { + tempfilebuffer1[x].len=fileRef->Read(tempfilebuffer1[x].data,kIOBufferSize,false); + readpoint+=tempfilebuffer1[x].len; + } + + fileRef->Seek ( writepoint, kXMP_SeekFromStart ); + fileRef->Write( (void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length())); + writepoint+=static_cast<XMP_Uns32>(outStr.length()); + size_t y=0; + bool continueread=(tempfilebuffer1[bufSize-1].len==kIOBufferSize); + size_t loop=bufSize; + while(loop) + { + if(continueread) + { + fileRef->Seek ( readpoint, kXMP_SeekFromStart ); + temp.len=fileRef->Read(temp.data,kIOBufferSize,false); + readpoint+=temp.len; + } + fileRef->Seek ( writepoint, kXMP_SeekFromStart ); + fileRef->Write(tempfilebuffer1[y].data,tempfilebuffer1[y].len); + writepoint+=tempfilebuffer1[y].len; + if (continueread) + tempfilebuffer1[y]=temp; + else + --loop; + if (temp.len<kIOBufferSize)continueread=false; + y=(y+1)%bufSize; + } + + modifyHeader(fileRef,extrapacketlength,packetInfo.offset ); + } + else + { + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) (packetInfo.offset) ); + if ( ! tempRef ) tempRef=fileRef->DeriveTemp(); + //copy data till xmp Packet + fileRef->Seek(0,kXMP_SeekFromStart); + XIO::Copy ( fileRef, tempRef, packetInfo.offset, this->parent->abortProc, this->parent->abortArg ); + + //insert the new XMP packet + fileRef->Seek(packetInfo.offset+packetInfo.length,kXMP_SeekFromStart); + tempRef->Write((void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length())); + + //copy the rest of data + pos=fileRef->Length(); + XIO::Copy ( fileRef, tempRef,pos-packetInfo.offset-packetInfo.length, this->parent->abortProc, this->parent->abortArg ); + modifyHeader(tempRef,extrapacketlength,packetInfo.offset ); + } +} + +// ================================================================================================= +// PostScript_MetaHandler::DetermineInsertionOffsets +// ============================================= +// +// Method determines the offsets at which the new xpacket and other postscript code be inserted +void PostScript_MetaHandler::DetermineInsertionOffsets(XMP_Int64& ADOhintOffset,XMP_Int64& InjectData1Offset, + XMP_Int64& InjectData3Offset) +{ + //find the position to place ADOContainsXMP hint + if(psHint!=kPSHint_MainFirst && (fileformat==kXMP_EPSFile||kXMPFiles_UnknownLength==packetInfo.offset)) + { + TokenLocation& tokenLoc= getTokenInfo(kPS_ADOContainsXMP); + if(tokenLoc.offsetStart==-1) + { + TokenLocation& tokenLoc1= getTokenInfo(kPS_EndComments); + if(tokenLoc1.offsetStart==-1) + { + //should never reach here + throw XMP_Error(kXMPErr_BadFileFormat,"%%EndComment Missing"); + } + ADOhintOffset=tokenLoc1.offsetStart; + } + else + { + ADOhintOffset= tokenLoc.offsetStart; + } + } + else if (psHint!=kPSHint_MainLast &&fileformat==kXMP_PostScriptFile) + { + TokenLocation& tokenLoc= getTokenInfo(kPS_ADOContainsXMP); + if(tokenLoc.offsetStart==-1) + { + TokenLocation& tokenLoc1= getTokenInfo(kPS_EndComments); + if(tokenLoc1.offsetStart==-1) + { + //should never reach here + throw XMP_Error(kXMPErr_BadFileFormat,"%%EndComment Missing"); + } + ADOhintOffset=tokenLoc1.offsetStart; + } + else + { + ADOhintOffset= tokenLoc.offsetStart; + } + } + //Find the location to insert kEPS_Injectdata1 + XMP_Uns64 xpacketLoc; + if ( (fileformat == kXMP_PostScriptFile) && (kXMPFiles_UnknownLength != packetInfo.offset) ) + { + xpacketLoc = (XMP_Uns64)lastPacketInfo.offset; + TokenLocation& endPagsetuploc = getTokenInfo(kPS_EndPageSetup); + if ( (endPagsetuploc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)endPagsetuploc.offsetStart) ) + { + InjectData1Offset=endPagsetuploc.offsetStart; + } + else + { + TokenLocation& trailerloc= getTokenInfo(kPS_Trailer); + if ( (trailerloc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)trailerloc.offsetStart) ) + { + InjectData1Offset=trailerloc.offsetStart; + } + else + { + TokenLocation& eofloc= getTokenInfo(kPS_EOF); + if ( (eofloc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)eofloc.offsetStart) ) + { + InjectData1Offset=eofloc.offsetStart; + } + else + { + TokenLocation& endPostScriptloc= getTokenInfo(kPS_EndPostScript); + if ( (endPostScriptloc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)endPostScriptloc.offsetStart) ) + { + InjectData1Offset=endPostScriptloc.offsetStart; + } + } + } + } + } + else + { + xpacketLoc = (XMP_Uns64)firstPacketInfo.offset; + TokenLocation& endPagsetuploc = getTokenInfo(kPS_EndPageSetup); + if ( (endPagsetuploc.offsetStart > -1) && (xpacketLoc > (XMP_Uns64)endPagsetuploc.offsetStart) ) + { + InjectData1Offset=endPagsetuploc.offsetStart; + } + else + { + TokenLocation& beginPagsetuploc= getTokenInfo(kPS_BeginPageSetup); + if ( (beginPagsetuploc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(beginPagsetuploc.offsetStart + beginPagsetuploc.tokenlen)) ) + { + InjectData1Offset=beginPagsetuploc.offsetStart+beginPagsetuploc.tokenlen; + } + else + { + TokenLocation& endPageCommentsloc= getTokenInfo(kPS_EndPageComments); + if ( (endPageCommentsloc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(endPageCommentsloc.offsetStart + endPageCommentsloc.tokenlen)) ) + { + InjectData1Offset=endPageCommentsloc.offsetStart+endPageCommentsloc.tokenlen; + } + else + { + TokenLocation& pageLoc= getTokenInfo(kPS_Page); + if ( (pageLoc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(pageLoc.offsetStart + pageLoc.tokenlen)) ) + { + InjectData1Offset=pageLoc.offsetStart+pageLoc.tokenlen; + } + else + { + TokenLocation& endSetupLoc= getTokenInfo(kPS_EndSetup); + if ( (endSetupLoc.offsetStart > -1) && (xpacketLoc > (XMP_Uns64)endSetupLoc.offsetStart) ) + { + InjectData1Offset=endSetupLoc.offsetStart; + } + else + { + TokenLocation& beginSetupLoc= getTokenInfo(kPS_BeginSetup); + if ( (beginSetupLoc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(beginSetupLoc.offsetStart + beginSetupLoc.tokenlen)) ) + { + InjectData1Offset=beginSetupLoc.offsetStart+beginSetupLoc.tokenlen; + } + else + { + TokenLocation& endPrologLoc= getTokenInfo(kPS_EndProlog); + if ( (endPrologLoc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(endPrologLoc.offsetStart + endPrologLoc.tokenlen)) ) + { + InjectData1Offset=endPrologLoc.offsetStart+endPrologLoc.tokenlen; + } + else + { + TokenLocation& endCommentsLoc= getTokenInfo(kPS_EndComments); + if ( (endCommentsLoc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(endCommentsLoc.offsetStart + endCommentsLoc.tokenlen)) ) + { + InjectData1Offset=endCommentsLoc.offsetStart+endCommentsLoc.tokenlen; + } + else + { + //should never reach here + throw XMP_Error(kXMPErr_BadFileFormat,"%%EndComment Missing"); + } + } + + } + } + } + } + } + } + } + + + //Find the location to insert kEPS_Injectdata3 + TokenLocation& trailerloc= getTokenInfo(kPS_Trailer); + if(trailerloc.offsetStart>-1 ) + { + InjectData3Offset=trailerloc.offsetStart+trailerloc.tokenlen; + } + else + { + TokenLocation& eofloc= getTokenInfo(kPS_EOF); + if(eofloc.offsetStart>-1 ) + { + InjectData3Offset=eofloc.offsetStart; + } + else + { + TokenLocation& endPostScriptloc= getTokenInfo(kPS_EndPostScript); + if(endPostScriptloc.offsetStart>-1 ) + { + InjectData3Offset=endPostScriptloc.offsetStart; + } + } + } +} + +// ================================================================================================= +// PostScript_MetaHandler::InsertNewUpdate +// ======================================= +// +// Method inserts a new Xpacket in the postscript file.This will be called in two cases +// a) If there is no xpacket in the PS file +// b) If the existing xpacket is embedded using readstring or readline method +void PostScript_MetaHandler::InsertNewUpdate (std::string &outStr,XMP_IO* &tempRef,bool doSafeUpdate ) +{ + // In this case it is better to have safe update + // as non-safe update implementation is going to be complex + // and more time consuming + // ignoring doSafeUpdate for this update method + + //No SubFileDecode Filter + // Need to insert new Metadata before existing metadata + // with SubFileDecode Filter + + XMP_IO* fileRef = this->parent->ioRef; + if ( ! tempRef ) tempRef=fileRef->DeriveTemp(); + //inject metadata at the right place + XMP_Int64 ADOhintOffset=-1,InjectData1Offset=-1,InjectData3Offset=-1; + DetermineInsertionOffsets(ADOhintOffset,InjectData1Offset,InjectData3Offset); + XMP_Int64 tempInjectData1Offset=InjectData1Offset; + fileRef->Rewind(); + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) + { + progressTracker->AddTotalWork ((float) ( fileRef->Length() +outStr.length() + 14) ); + if (fileformat==kXMP_EPSFile) + { + progressTracker->AddTotalWork ((float) ( kEPS_Injectdata1.length() + kEPS_Injectdata2.length() + kEPS_Injectdata3.length()) ); + } + else + { + progressTracker->AddTotalWork ((float) ( kPS_Injectdata1.length() + kPS_Injectdata2.length()) ); + } + } + XMP_Int64 totalReadLength=0; + //copy contents from orignal file to Temp File + if(ADOhintOffset!=-1) + { + XIO::Copy(fileRef,tempRef,ADOhintOffset,this->parent->abortProc,this->parent->abortArg); + totalReadLength+=ADOhintOffset; + if (fileformat==kXMP_EPSFile || kXMPFiles_UnknownLength==packetInfo.offset) + { + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) ( kPS_XMPHintMainFirst.length()) ); + tempRef->Write(kPS_XMPHintMainFirst.c_str(),kPS_XMPHintMainFirst.length()); + } + else + { + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) ( kPS_XMPHintMainLast.length()) ); + tempRef->Write(kPS_XMPHintMainLast.c_str(),kPS_XMPHintMainLast.length()); + } + } + InjectData1Offset-=totalReadLength; + XIO::Copy(fileRef,tempRef,InjectData1Offset,this->parent->abortProc,this->parent->abortArg); + totalReadLength+=InjectData1Offset; + if (fileformat==kXMP_EPSFile) + { + tempRef->Write(kEPS_Injectdata1.c_str(),kEPS_Injectdata1.length()); + tempRef->Write((void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length())); + tempRef->Write(kEPS_Injectdata2.c_str(),kEPS_Injectdata2.length()); + } + else + { + tempRef->Write(kPS_Injectdata1.c_str(),kPS_Injectdata1.length()); + tempRef->Write((void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length())); + tempRef->Write(kPS_Injectdata2.c_str(),kPS_Injectdata2.length()); + } + if (InjectData3Offset!=-1) + { + InjectData3Offset-=totalReadLength; + XIO::Copy(fileRef,tempRef,InjectData3Offset,this->parent->abortProc,this->parent->abortArg); + totalReadLength+=InjectData3Offset; + if (fileformat==kXMP_EPSFile) + { + tempRef->Write(kEPS_Injectdata3.c_str(),kEPS_Injectdata3.length()); + } + XMP_Int64 remlength=fileRef->Length()-totalReadLength; + XIO::Copy(fileRef,tempRef,remlength,this->parent->abortProc,this->parent->abortArg); + totalReadLength+=remlength; + } + else + { + XMP_Int64 remlength=fileRef->Length()-totalReadLength; + XIO::Copy(fileRef,tempRef,remlength,this->parent->abortProc,this->parent->abortArg); + totalReadLength+=remlength; + if (fileformat==kXMP_EPSFile) + { + tempRef->Write(kEPS_Injectdata3.c_str(),kEPS_Injectdata3.length()); + } + } + XMP_Int64 extraBytes; + if (fileformat==kXMP_EPSFile ) + { + extraBytes=((ADOhintOffset!=-1)?kPS_XMPHintMainFirst.length():0)+kEPS_Injectdata3.length()+kEPS_Injectdata2.length()+ + kEPS_Injectdata1.length()+outStr.length(); + } + else + { + extraBytes=((ADOhintOffset!=-1)?(kXMPFiles_UnknownLength!=packetInfo.offset?kPS_XMPHintMainLast.length():kPS_XMPHintMainFirst.length()):0)+kPS_Injectdata2.length()+kPS_Injectdata1.length()+outStr.length(); + } + modifyHeader(tempRef,extraBytes,tempInjectData1Offset ); +} + +// ================================================================================================= +// PostScript_MetaHandler::UpdateFile +// ================================== +// +// Virtual Method implementation to update XMP metadata in a PS file +void PostScript_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + IgnoreParam ( doSafeUpdate ); + if ( ! this->needsUpdate ) return; + + XMP_IO * tempRef = 0; + XMP_IO* fileRef = this->parent->ioRef; + std::string & xmpPacket = this->xmpPacket; + std::string outStr; + + if (!fileRef ) + { + XMP_Throw ( "Invalid File Refernce Cannot update XMP", kXMPErr_BadOptions ); + } + bool localProgressTracking = false; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) + { + if ( ! progressTracker->WorkInProgress() ) + { + localProgressTracking = true; + progressTracker->BeginWork (); + } + } + try + { switch(DetermineUpdateMethod(outStr)) + { + case kPS_Inplace: + { + InplaceUpdate ( outStr, tempRef, doSafeUpdate ); + break; + } + case kPS_ExpandSFDFilter: + { + ExpandingSFDFilterUpdate ( outStr, tempRef, doSafeUpdate ); + break; + } + case kPS_InjectNew: + { + InsertNewUpdate ( outStr, tempRef, doSafeUpdate ); + break; + } + case kPS_None: + default: + { + XMP_Throw ( "XMP Write Failed ", kXMPErr_BadOptions ); + } + } + } + catch(...) + { + if( tempRef ) fileRef->DeleteTemp(); + throw; + } + // rename the modified temp file and then delete the temp file + if ( tempRef ) fileRef->AbsorbTemp(); + if ( localProgressTracking ) progressTracker->WorkComplete(); + this->needsUpdate = false; + +} // PostScript_MetaHandler::UpdateFile + + +// ================================================================================================= +// PostScript_MetaHandler::UpdateFile +// ================================== +// +// Method to write the file with updated XMP metadata to the passed temp file reference +void PostScript_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(); + + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) progressTracker->BeginWork ((float) fileLen ); + 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; + } + + if ( progressTracker != 0 ) progressTracker->WorkComplete(); +} +// ================================================================================================= diff --git a/XMPFiles/source/FileHandlers/PostScript_Handler.hpp b/XMPFiles/source/FileHandlers/PostScript_Handler.hpp index 8813336..7bff0fc 100644 --- a/XMPFiles/source/FileHandlers/PostScript_Handler.hpp +++ b/XMPFiles/source/FileHandlers/PostScript_Handler.hpp @@ -15,7 +15,9 @@ #include "public/include/XMP_Const.h" #include "public/include/XMP_IO.hpp" -#include "XMPFiles/source/FileHandlers/Trivial_Handler.hpp" +#include "XMPFiles/source/FormatSupport/PostScript_Support.hpp" + + // ================================================================================================= /// \file PostScript_Handler.hpp @@ -25,8 +27,6 @@ /// // ================================================================================================= -// *** 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, @@ -34,33 +34,112 @@ extern bool PostScript_CheckFormat ( XMP_FileFormat format, 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 +static const XMP_OptionBits kPostScript_HandlerFlags = ( + kXMPFiles_CanInjectXMP + |kXMPFiles_CanExpand + |kXMPFiles_CanRewrite + |kXMPFiles_PrefersInPlace + |kXMPFiles_CanReconcile + |kXMPFiles_AllowsOnlyXMP + |kXMPFiles_ReturnsRawPacket + |kXMPFiles_AllowsSafeUpdate + |kXMPFiles_CanNotifyProgress ); + +class PostScript_MetaHandler : public XMPFileHandler { public: PostScript_MetaHandler ( XMPFiles * parent ); ~PostScript_MetaHandler(); - void CacheFileData(); + void CacheFileData(); + void UpdateFile ( bool doSafeUpdate ); + void ProcessXMP ( ); + void WriteTempFile ( XMP_IO* tempRef ); int psHint; - + /* Structure used to keep + Track of Tokens in + EPS files + */ + struct TokenLocation{ + //offset from the begining of the file + // at which the token string starts + XMP_Int64 offsetStart; + //Total length of the token string + XMP_Int64 tokenlen; + TokenLocation():offsetStart(-1),tokenlen(0) + {} + }; protected: - + //Determines the postscript hint in the DSC comments int FindPostScriptHint(); + // Helper methods to get the First or the Last packet from the + // PS file based upon the PostScript hint that is present in the PS file bool FindFirstPacket(); bool FindLastPacket(); + + //Facilitates read time reconciliation of PS native metadata + void ReconcileXMP( const std::string &xmpStr, std::string *outStr ); + + //Facilitates reading of XMP packet , if one exists + void ReadXMPPacket ( std::string & xmpPacket); + + // Parses the PS file to record th epresence and location of + // XMP packet and native metadata in the file + void ParsePSFile(); + + // Helper function to record the native metadata key/avlue pairs + // when parsing the PS file + void RegisterKeyValue(std::string& key, std::string& value); + + // Helper Function to record the location and length of the Tokens + // in the opened PS file + void setTokenInfo(TokenFlag tFlag,XMP_Int64 offset,XMP_Int64 length); + + // Getter to get the location of a token ina PS file. + TokenLocation& getTokenInfo(TokenFlag tFlag); + + //modifies the Binary Header of a PS file as per the modifications + void modifyHeader(XMP_IO* fileRef,XMP_Int64 extrabytes,XMP_Int64 offset ); + + //Extract the values for different DSC comments + bool ExtractDSCCommentValue(IOBuffer &ioBuf,NativeMetadataIndex index); + + //Extract value for ADO_ContainsXMP Comment + bool ExtractContainsXMPHint(IOBuffer &ioBuf,XMP_Int64 containsXMPStartpos); + + //Extract values from DocInfo Dict + bool ExtractDocInfoDict(IOBuffer &ioBuf); + + //Determine the update method to be used + UpdateMethod DetermineUpdateMethod(std::string & outStr); + void DetermineInsertionOffsets(XMP_Int64& ADOhintOffset,XMP_Int64& InjectData1Offset, + XMP_Int64& InjectData3Offset); + //Different update methods + void InplaceUpdate (std::string &outStr,XMP_IO* &tempRef, bool doSafeUpdate); + void ExpandingSFDFilterUpdate (std::string &outStr,XMP_IO* &tempRef, bool doSafeUpdate ); + void InsertNewUpdate ( std::string &outStr,XMP_IO* &tempRef, bool doSafeUpdate ); +private: + //Flag tracks DSC comments + XMP_Uns32 dscFlags; + //Flag tracks DOCINFO keys + XMP_Uns32 docInfoFlags; + //stores the native metadata values. Index values an enum var NativeMetadataIndex + std::string nativeMeta[kPS_MaxNativeIndexValue]; + //all offsets are to the end of the comment after atleast one whitespace + TokenLocation fileTokenInfo[25]; + //Indicates the presence of both XMP hint and XMP + bool containsXMPHint; + //Keeps track whether a PS or EPS + XMP_FileFormat fileformat; + //keep the first packet info + XMP_PacketInfo firstPacketInfo; + //keep the last packet info + XMP_PacketInfo lastPacketInfo; + }; // PostScript_MetaHandler // ================================================================================================= diff --git a/XMPFiles/source/FileHandlers/SWF_Handler.cpp b/XMPFiles/source/FileHandlers/SWF_Handler.cpp index a0554ce..3266e94 100644 --- a/XMPFiles/source/FileHandlers/SWF_Handler.cpp +++ b/XMPFiles/source/FileHandlers/SWF_Handler.cpp @@ -53,7 +53,7 @@ bool SWF_CheckFormat ( XMP_FileFormat format, // Make sure the file is long enough for an empty SWF stream. Check the signature. - if ( fileRef->Length() < SWF_IO::HeaderPrefixSize ) return false; + if ( fileRef->Length() < (XMP_Int64)SWF_IO::HeaderPrefixSize ) return false; fileRef->Rewind(); XMP_Uns8 buffer [4]; @@ -306,12 +306,19 @@ void SWF_MetaHandler::UpdateFile ( bool doSafeUpdate ) this->hasMetadata = true; - // Rewrite the file. + // Update the uncompressed file length and rewrite the file. + PutUns32LE ( this->expandedSWF.size(), &this->expandedSWF[4] ); + XMP_IO * fileRef = this->parent->ioRef; fileRef->Rewind(); fileRef->Truncate ( 0 ); - fileRef->Write ( &this->expandedSWF[0], this->expandedSWF.size() ); + + if ( this->isCompressed ) { + SWF_IO::CompressMemoryToFile ( this->expandedSWF, fileRef ); + } else { + fileRef->Write ( &this->expandedSWF[0], this->expandedSWF.size() ); + } } // SWF_MetaHandler::UpdateFile diff --git a/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp b/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp index 863eab7..9d9a9d4 100644 --- a/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp +++ b/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp @@ -15,9 +15,10 @@ #include "XMPFiles/source/XMPFiles_Impl.hpp" #include "source/XMPFiles_IO.hpp" #include "source/XIO.hpp" +#include "source/IOUtils.hpp" #include "XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp" - +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" #include "third-party/zuid/interfaces/MD5.h" #if XMP_WinBuild @@ -535,7 +536,7 @@ SonyHDV_MetaHandler::SonyHDV_MetaHandler ( XMPFiles * _parent ) if ( this->parent->tempPtr == 0 ) { // The CheckFormat call might have been skipped. - this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath ); + this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() ); } this->rootPath.assign ( (char*) this->parent->tempPtr ); @@ -581,6 +582,24 @@ bool SonyHDV_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr s } // SonyHDV_MetaHandler::MakeClipFilePath +// This method removes the timestamp information from a clip name. It returns the clip name with a following "_". +// For example: The clip name "00_0001_2007-08-06_165555" becomes "00_0001_". +static void RemoveTimeStampFromClipName(std::string &clipName) +{ + int usCount = 0; + size_t i, limit = clipName.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. +} + // ================================================================================================= // SonyHDV_MetaHandler::MakeIndexFilePath // ====================================== @@ -608,18 +627,7 @@ bool SonyHDV_MetaHandler::MakeIndexFilePath ( std::string& idxPath, const std::s // 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. + RemoveTimeStampFromClipName(clipName); Host_IO::AutoFolder aFolder; std::string childName; @@ -721,6 +729,101 @@ bool SonyHDV_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) } // SonyHDV_MetaHandler::GetFileModDate // ================================================================================================= +// SonyHDV_MetaHandler::FillMetadataFiles +// ================================ +void SonyHDV_MetaHandler::FillMetadataFiles ( std::vector<std::string>* metadataFiles ) +{ + std::string noExtPath, filePath; + + noExtPath = rootPath + kDirChar + "VIDEO" + kDirChar + "HVR" + kDirChar + clipName; + + filePath = noExtPath + ".XMP"; + metadataFiles->push_back ( filePath ); + filePath = noExtPath + ".IDX"; + metadataFiles->push_back ( filePath ); + +} // FillMetadataFiles_SonyHDV + +// ================================================================================================= +// SonyHDV_MetaHandler::IsMetadataWritable +// ======================================= + +bool SonyHDV_MetaHandler::IsMetadataWritable ( ) +{ + std::vector<std::string> metadataFiles; + FillMetadataFiles(&metadataFiles); + std::vector<std::string>::iterator itr = metadataFiles.begin(); + // Check whether sidecar is writable, if not then check if it can be created. + return Host_IO::Writable( itr->c_str(), true ); +}// SonyHDV_MetaHandler::IsMetadataWritable + + +// ================================================================================================= +// SonyHDV_MetaHandler::FillAssociatedResources +// ====================================== +// +// This method returns all clip associated "media files","index files" whose name +// starts with XX_CCCC_ and side cars starting with XX_CCCC. +void SonyHDV_MetaHandler::FillAssociatedResources ( std::vector<std::string> * resourceList ) +{ + // The possible associated resources: + // VIDEO/ + // HVR/ + // XX_CCCC_YYYY-MM-DD_hhmmss.M2T // HDV media + // XX_CCCC_YYYY-MM-DD_hhmmss.IDX // Metadata Index file + // XX_CCCC_YYYY-MM-DD_hhmmss.XMP // sidecar + // + // XX_CCCC_YYYY-MM-DD_hhmmss.AVI // DV(AVI) medi + // XX_CCCC_YYYY-MM-DD_hhmmss.IDX // Metadata Index file + // XX_CCCC_YYYY-MM-DD_hhmmss.XMP // sidecar + // + // XX_CCCC_YYYY-MM-DD_hhmmss.DV // DV(RAW) media + // XX_CCCC_YYYY-MM-DD_hhmmss.IDX // Metadata Index file + // XX_CCCC_YYYY-MM-DD_hhmmss.XMP // sidecar + // + // tracks.dat // Clip database file + + std:: string hvrPath = this->rootPath + kDirChar + "VIDEO" + kDirChar + "HVR"; + std::string filePath; + + //Add RootPath + filePath = this->rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists( resourceList, filePath ); + + // If XX_CCCC_YYYY-MM-DD_hhmmss is clip name then we remove YYYY-MM-DD_hhmmss from this and return + // all files starting with XX_CCCC_ and having required extension. + std::string clipNameWithoutTimeStamp = this->clipName; + RemoveTimeStampFromClipName(clipNameWithoutTimeStamp); + + // Add media files. + // We don't know the extension of the media so we will check for all + // three possible extensions and add whichever is existing. + + // "AddResourceIfExists" will add all spanned clips that match the clip prefix "clipNameWithoutTimeStamp" + // and specified extensions. + PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".M2T"); + + PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".AVI"); + + PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".DV"); + + // Add Index files. + PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".IDX"); + + // Add sidecars. + // For sidecars we will look for XX_CCCC*.XMP instead of XX_CCCC_*.XMP because we may generate such files + // in case of spanning (in future) or logical paths. + clipNameWithoutTimeStamp.erase(clipNameWithoutTimeStamp.end()-1); + PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".XMP"); + + //Add clip database file + filePath = hvrPath + kDirChar + "tracks.dat"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + +} // SonyHDV_MetaHandler::FillAssociatedResources + + +// ================================================================================================= // SonyHDV_MetaHandler::CacheFileData // ================================== @@ -729,22 +832,23 @@ 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 ); + XMP_Throw ( "SonyHDV 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. + if ( ! Host_IO::Exists ( xmpPath.c_str() ) ) return; // No XMP. - // Read the entire .XMP file. + // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 + // only if the file does not exist. 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. + if ( xmpFile == 0 ) XMP_Throw ( "SonyHDV XMP file open failure", kXMPErr_InternalFailure ); this->parent->ioRef = xmpFile; XMP_Int64 xmpLen = xmpFile->Length(); diff --git a/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp b/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp index ac27c66..190930d 100644 --- a/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp +++ b/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp @@ -49,6 +49,9 @@ class SonyHDV_MetaHandler : public XMPFileHandler public: bool GetFileModDate ( XMP_DateTime * modDate ); + void FillMetadataFiles(std::vector<std::string>* metadataFiles ); + void FillAssociatedResources ( std::vector<std::string> * resourceList ); + bool IsMetadataWritable ( ); void CacheFileData(); void ProcessXMP(); diff --git a/XMPFiles/source/FileHandlers/TIFF_Handler.cpp b/XMPFiles/source/FileHandlers/TIFF_Handler.cpp index 109fe43..a09b879 100644 --- a/XMPFiles/source/FileHandlers/TIFF_Handler.cpp +++ b/XMPFiles/source/FileHandlers/TIFF_Handler.cpp @@ -53,13 +53,14 @@ bool TIFF_CheckFormat ( XMP_FileFormat format, enum { kMinimalTIFFSize = 4+4+2+12+4 }; // Header plus IFD with 1 entry. - IOBuffer ioBuf; - fileRef->Rewind ( ); - if ( ! CheckFileSpace ( fileRef, &ioBuf, kMinimalTIFFSize ) ) return false; + if ( ! XIO::CheckFileSpace ( fileRef, kMinimalTIFFSize ) ) return false; - bool leTIFF = CheckBytes ( ioBuf.ptr, "\x49\x49\x2A\x00", 4 ); - bool beTIFF = CheckBytes ( ioBuf.ptr, "\x4D\x4D\x00\x2A", 4 ); + XMP_Uns8 buffer [4]; + fileRef->Read ( buffer, 4 ); + + bool leTIFF = CheckBytes ( buffer, "\x49\x49\x2A\x00", 4 ); + bool beTIFF = CheckBytes ( buffer, "\x4D\x4D\x00\x2A", 4 ); return (leTIFF | beTIFF); @@ -264,14 +265,8 @@ void TIFF_MetaHandler::ProcessXMP() 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. - } + } catch ( ... ) { /* Ignore parsing failures, someday we hope to get partial XMP back. */ } + haveXMP = true; } // Process the legacy metadata. @@ -331,6 +326,9 @@ void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength)); if ( this->tiffMgr.IsLegacyChanged() ) doInPlace = false; + + bool localProgressTracking = false; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; if ( ! doInPlace ) { @@ -338,8 +336,13 @@ void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) sAPIPerf->back().extraInfo += ", TIFF append update"; #endif + if ( (progressTracker != 0) && (! progressTracker->WorkInProgress()) ) { + localProgressTracking = true; + progressTracker->BeginWork(); + } + this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, (XMP_Uns32)this->xmpPacket.size(), this->xmpPacket.c_str() ); - this->tiffMgr.UpdateFileStream ( destRef ); + this->tiffMgr.UpdateFileStream ( destRef, progressTracker ); } else { @@ -357,11 +360,21 @@ void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) XMP_Assert ( this->xmpPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic. + if ( progressTracker != 0 ) { + if ( progressTracker->WorkInProgress() ) { + progressTracker->AddTotalWork ( this->xmpPacket.size() ); + } else { + localProgressTracking = true; + progressTracker->BeginWork ( this->xmpPacket.size() ); + } + } + liveFile->Seek ( oldPacketOffset, kXMP_SeekFromStart ); liveFile->Write ( this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() ); } - + + if ( localProgressTracking ) progressTracker->WorkComplete(); this->needsUpdate = false; } // TIFF_MetaHandler::UpdateFile @@ -384,6 +397,9 @@ void TIFF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) if ( fileLen > 0xFFFFFFFFLL ) { // Check before making a copy of the file. XMP_Throw ( "TIFF fles can't exceed 4GB", kXMPErr_BadTIFF ); } + + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)fileLen ); origRef->Rewind ( ); tempRef->Truncate ( 0 ); @@ -397,6 +413,8 @@ void TIFF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) this->parent->ioRef = origRef; throw; } + + if ( progressTracker != 0 ) progressTracker->WorkComplete(); } // TIFF_MetaHandler::WriteTempFile diff --git a/XMPFiles/source/FileHandlers/TIFF_Handler.hpp b/XMPFiles/source/FileHandlers/TIFF_Handler.hpp index 40dc50a..bc653b2 100644 --- a/XMPFiles/source/FileHandlers/TIFF_Handler.hpp +++ b/XMPFiles/source/FileHandlers/TIFF_Handler.hpp @@ -38,7 +38,8 @@ static const XMP_OptionBits kTIFF_HandlerFlags = (kXMPFiles_CanInjectXMP | kXMPFiles_CanReconcile | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | - kXMPFiles_AllowsSafeUpdate); + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress); class TIFF_MetaHandler : public XMPFileHandler { diff --git a/XMPFiles/source/FileHandlers/UCF_Handler.cpp b/XMPFiles/source/FileHandlers/UCF_Handler.cpp index c9f63b9..8e1e1ff 100644 --- a/XMPFiles/source/FileHandlers/UCF_Handler.cpp +++ b/XMPFiles/source/FileHandlers/UCF_Handler.cpp @@ -128,7 +128,9 @@ bool UCF_CheckFormat ( XMP_FileFormat format, 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 - + XMP_LitMatch( mimetype, "application/vnd.adobe.collage" ) || //Adobe Collage + XMP_LitMatch( mimetype, "application/vnd.adobe.ideas" ) || //Adobe Ideas + XMP_LitMatch( mimetype, "application/vnd.adobe.proto" ) || //Adobe Proto false ) // "sentinel" // *** ==> unknown are also treated as not acceptable diff --git a/XMPFiles/source/FileHandlers/WAVE_Handler.cpp b/XMPFiles/source/FileHandlers/WAVE_Handler.cpp index 5f1b330..876234e 100644 --- a/XMPFiles/source/FileHandlers/WAVE_Handler.cpp +++ b/XMPFiles/source/FileHandlers/WAVE_Handler.cpp @@ -434,9 +434,23 @@ void WAVE_MetaHandler::UpdateFile ( bool doSafeUpdate ) } } // XMP Packet is never completely removed from the file. - + + XMP_ProgressTracker* progressTracker=this->parent->progressTracker; + // local progess tracking required because for Handlers incapable of + // kXMPFiles_CanRewrite XMPFiles call this Update method after making + // a copy of the orignal file + bool localProgressTracking=false; + if ( progressTracker != 0 ) + { + if ( ! progressTracker->WorkInProgress() ) + { + localProgressTracking = true; + progressTracker->BeginWork (); + } + } //write tree back to file - mChunkController->writeFile( this->parent->ioRef ); + mChunkController->writeFile( this->parent->ioRef ,progressTracker); + if ( localProgressTracking && progressTracker != 0 ) progressTracker->WorkComplete(); this->needsUpdate = false; // Make sure this is only called once. } // WAVE_MetaHandler::UpdateFile diff --git a/XMPFiles/source/FileHandlers/WAVE_Handler.hpp b/XMPFiles/source/FileHandlers/WAVE_Handler.hpp index 49f873c..f8cc31b 100644 --- a/XMPFiles/source/FileHandlers/WAVE_Handler.hpp +++ b/XMPFiles/source/FileHandlers/WAVE_Handler.hpp @@ -52,7 +52,8 @@ static const XMP_OptionBits kWAVE_HandlerFlags = (kXMPFiles_CanInjectXMP | kXMPFiles_PrefersInPlace | kXMPFiles_CanReconcile | kXMPFiles_ReturnsRawPacket | - kXMPFiles_AllowsSafeUpdate + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress ); /** diff --git a/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp index 350ab1f..9005fe8 100644 --- a/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp +++ b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp @@ -15,10 +15,12 @@ #include "XMPFiles/source/XMPFiles_Impl.hpp" #include "source/XMPFiles_IO.hpp" #include "source/XIO.hpp" +#include "source/IOUtils.hpp" #include "XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp" #include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp" #include "third-party/zuid/interfaces/MD5.h" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" using namespace std; @@ -212,7 +214,7 @@ XMPFileHandler * XDCAMEX_MetaHandlerCTor ( XMPFiles * parent ) // XDCAMEX_MetaHandler::XDCAMEX_MetaHandler // ======================================== -XDCAMEX_MetaHandler::XDCAMEX_MetaHandler ( XMPFiles * _parent ) : expat(0) +XDCAMEX_MetaHandler::XDCAMEX_MetaHandler ( XMPFiles * _parent ) : expat(0),clipMetadata(0) { this->parent = _parent; // Inherited, can't set in the prefix. this->handlerFlags = kXDCAMEX_HandlerFlags; @@ -222,7 +224,7 @@ XDCAMEX_MetaHandler::XDCAMEX_MetaHandler ( XMPFiles * _parent ) : expat(0) if ( this->parent->tempPtr == 0 ) { // The CheckFormat call might have been skipped. - this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath ); + this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() ); } this->rootPath.assign ( (char*) this->parent->tempPtr ); @@ -351,7 +353,8 @@ void XDCAMEX_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) void XDCAMEX_MetaHandler::CleanupLegacyXML() { - if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; } + delete this->expat; + this->expat = 0; clipMetadata = 0; // ! Was a pointer into the expat tree. @@ -407,6 +410,152 @@ bool XDCAMEX_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) } // XDCAMEX_MetaHandler::GetFileModDate +// Adds all the associated resources for the specified clip only (not related spanned ones) +static void FillClipAssociatedResources( std::vector<std::string> * resourceList, std::string &clipPath, std::string &clipName ) +{ + std::string filePath; + std::string spannedClipFolderPath = clipPath + clipName + kDirChar; + + std::string clipPathNoExt = spannedClipFolderPath + clipName; + // Get the files present inside clip folder. + std::vector<std::string> regExpStringVec; + std::string regExpString; + regExpString = "^" + clipName + ".MP4$"; + regExpStringVec.push_back(regExpString); + regExpString = "^" + clipName + "M\\d\\d.XMP$"; + regExpStringVec.push_back(regExpString); + regExpString = "^" + clipName + "M\\d\\d.XML$"; + regExpStringVec.push_back(regExpString); + regExpString = "^" + clipName + "I\\d\\d.PPN$"; + regExpStringVec.push_back(regExpString); + regExpString = "^" + clipName + "R\\d\\d.BIM$"; + regExpStringVec.push_back(regExpString); + regExpString = "^" + clipName + ".SMI$"; + regExpStringVec.push_back(regExpString); + + IOUtils::GetMatchingChildren (*resourceList, spannedClipFolderPath, regExpStringVec, false, true, true ); + +} + + +// ================================================================================================= +// XDCAMEX_MetaHandler::FillAssociatedResources +// ====================================== +void XDCAMEX_MetaHandler::FillAssociatedResources ( std::vector<std::string> * resourceList ) +{ + // The possible associated resources: + // BPAV/ + // MEDIAPRO.XML + // CUEUP.XML + // CLPR/ + // MIXXXX_YY: MI is MachineID, XXXX is TakeSerial, + // YY is ClipSuffix(as single take can be divided across multiple clips.) + // In case of spanning, all the clip folders starting from "MIXXXX_" are looked for. + // MIXXXX_YY.MP4 + // MIXXXX_YYMNN.XML NN is a counter which will start from from 01 and can go upto 99 based + // on number of files present in this folder with same extension. + // MIXXXX_YYMNN.XMP + // MIXXXX_YYINN.PPN + // MIXXXX_YYRNN.BIM + // MXXXX_YY.SMI + // TAKR/ + // MIXXXX: + // MIXXXXMNN.XML NN is a counter which will start from from 01 and can go upto 99 based + // on number of files present in this folder with same extension. + // MIXXXX.SMI + // MIXXXXUNN.SMI NN is a counter which goes from 01 to N-1 where N is number of media, this + // take is divided into. For Nth, MIXXXX.SMI shall be picked up. + XMP_VarString bpavPath = this->rootPath + kDirChar + "BPAV" + kDirChar; + XMP_VarString filePath; + //Add RootPath + filePath = this->rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + + // Get the files present directly inside BPAV folder. + filePath = bpavPath + "MEDIAPRO.XML"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + filePath = bpavPath + "MEDIAPRO.BUP"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + filePath = bpavPath + "CUEUP.XML"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + filePath = bpavPath + "CUEUP.BUP"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + + XMP_VarString clipPath = bpavPath + "CLPR" + kDirChar; + size_t clipSuffixIndex = this->clipName.find_last_of('_'); + XMP_VarString takeName = this->clipName.substr(0, clipSuffixIndex); + + // Add spanned clip files. + // Here, we iterate over all the folders present inside "/BPAV/CLPR/" and whose name starts from + // "MIXXXX_". All valid files present inside such folders are added to the list. + XMP_VarString regExpString; + regExpString = "^" + takeName + "_\\d\\d$"; + XMP_StringVector list; + + IOUtils::GetMatchingChildren ( list, clipPath, regExpString, true, false, false ); + size_t spaningClipsCount = list.size(); + for ( size_t index = 0; index < spaningClipsCount; index++ ) { + FillClipAssociatedResources ( resourceList, clipPath, list[index] ); + } + list.clear(); + + size_t sizeWithoutTakeFiles = resourceList->size(); + XMP_VarString takeFolderPath = bpavPath + "TAKR" + kDirChar + takeName + kDirChar; + XMP_StringVector regExpStringVec; + + // Get the files present inside take folder. + regExpString = "^" + takeName + "M\\d\\d.XML$"; + regExpStringVec.push_back ( regExpString ); + regExpString = "^" + takeName + "U\\d\\d.SMI$"; + regExpStringVec.push_back ( regExpString ); + regExpString = "^" + takeName + ".SMI$"; + regExpStringVec.push_back ( regExpString ); + IOUtils::GetMatchingChildren ( *resourceList, takeFolderPath, regExpStringVec, false, true, true ); + + if ( sizeWithoutTakeFiles == resourceList->size() ) + { + // no Take files added to resource list. But "TAKR" folder is necessary to recognize this format + // so let's add it to the list. + filePath = bpavPath + "TAKR" + kDirChar; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + } +} // XDCAMEX_MetaHandler::FillAssociatedResources + +// ================================================================================================= +// XDCAMEX_MetaHandler::FillMetadataFiles +// ====================================== +void XDCAMEX_MetaHandler::FillMetadataFiles ( std::vector<std::string> * metadataFiles ) +{ + std::string noExtPath, filePath; + + noExtPath = rootPath + kDirChar + "BPAV" + kDirChar + "CLPR" + + kDirChar + clipName + kDirChar + clipName; + + filePath = noExtPath + "M01.XMP"; + metadataFiles->push_back ( filePath ); + filePath = noExtPath + "M01.XML"; + metadataFiles->push_back ( filePath ); + filePath = rootPath + kDirChar + "BPAV" + kDirChar + "MEDIAPRO.XML"; + metadataFiles->push_back ( filePath ); + +} // FillMetadataFiles_XDCAM_EX + +// ================================================================================================= +// XDCAMEX_MetaHandler::IsMetadataWritable +// ======================================= + +bool XDCAMEX_MetaHandler::IsMetadataWritable ( ) +{ + std::vector<std::string> metadataFiles; + FillMetadataFiles(&metadataFiles); + std::vector<std::string>::iterator itr = metadataFiles.begin(); + // Check whether sidecar is writable, if not then check if it can be created. + XMP_Bool xmpWritable = Host_IO::Writable( itr->c_str(), true ); + // Check for legacy metadata file. + XMP_Bool xmlWritable = Host_IO::Writable( (++itr)->c_str(), false ); + return ( xmlWritable && xmpWritable ); +}// XDCAMEX_MetaHandler::IsMetadataWritable + // ================================================================================================= // XDCAMEX_MetaHandler::CacheFileData // ================================== @@ -423,15 +572,16 @@ void XDCAMEX_MetaHandler::CacheFileData() std::string xmpPath; this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); - if ( Host_IO::GetFileMode ( xmpPath.c_str() ) != Host_IO::kFMode_IsFile ) return; // No XMP. + if ( ! Host_IO::Exists ( xmpPath.c_str() ) ) return; // No XMP. - // Read the entire .XMP file. + // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 + // only if the file does not exist. 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. + if ( xmpFile == 0 ) XMP_Throw ( "XDCAMEX XMP file open failure", kXMPErr_InternalFailure ); this->parent->ioRef = xmpFile; XMP_Int64 xmpLen = xmpFile->Length(); @@ -463,7 +613,7 @@ void XDCAMEX_MetaHandler::GetTakeDuration ( const std::string & takeURI, std::st // *** Better yet, avoid this cruft with self-cleaning objects. #define CleanupAndExit \ { \ - if ( expat != 0 ) delete expat; \ + delete expatMediaPro; \ takeXMLFile.Close(); \ return; \ } @@ -500,22 +650,22 @@ void XDCAMEX_MetaHandler::GetTakeDuration ( const std::string & takeURI, std::st 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; + ExpatAdapter * expatMediaPro = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); + if ( expatMediaPro == 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 */ ); + expatMediaPro->ParseBuffer ( buffer, ioCount, false /* not the end */ ); } - expat->ParseBuffer ( 0, 0, true ); // End the parse. + expatMediaPro->ParseBuffer ( 0, 0, true ); // End the parse. takeXMLFile.Close(); // Get the root node of the XML tree. - XML_Node & mediaproXMLTree = expat->tree; + XML_Node & mediaproXMLTree = expatMediaPro->tree; for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) { if ( mediaproXMLTree.content[i]->kind == kElemNode ) { takeRootElem = mediaproXMLTree.content[i]; @@ -568,7 +718,7 @@ void XDCAMEX_MetaHandler::GetTakeUMID ( const std::string& clipUMID, // *** Better yet, avoid this cruft with self-cleaning objects. #define CleanupAndExit \ { \ - if (expat != 0) delete expat; \ + delete expatMediaPro; \ mediaproXMLFile.Close(); \ return; \ } @@ -593,22 +743,22 @@ void XDCAMEX_MetaHandler::GetTakeUMID ( const std::string& clipUMID, 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; + ExpatAdapter * expatMediaPro = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); + if ( expatMediaPro == 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 */ ); + expatMediaPro->ParseBuffer ( buffer, ioCount, false /* not the end */ ); } - expat->ParseBuffer ( 0, 0, true ); // End the parse. + expatMediaPro->ParseBuffer ( 0, 0, true ); // End the parse. mediaproXMLFile.Close(); // Get the root node of the XML tree. - XML_Node & mediaproXMLTree = expat->tree; + XML_Node & mediaproXMLTree = expatMediaPro->tree; for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) { if ( mediaproXMLTree.content[i]->kind == kElemNode ) { mediaproRootElem = mediaproXMLTree.content[i]; diff --git a/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp index a8ab89f..3673b1f 100644 --- a/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp +++ b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp @@ -46,6 +46,10 @@ class XDCAMEX_MetaHandler : public XMPFileHandler public: bool GetFileModDate ( XMP_DateTime * modDate ); + + void FillMetadataFiles ( std::vector<std::string> * metadataFiles ); + void FillAssociatedResources ( std::vector<std::string> * resourceList ); + bool IsMetadataWritable ( ); void CacheFileData(); void ProcessXMP(); @@ -61,7 +65,7 @@ public: private: - XDCAMEX_MetaHandler() : expat(0) {}; // Hidden on purpose. + XDCAMEX_MetaHandler() : 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 ); @@ -75,6 +79,7 @@ private: std::string rootPath, clipName, xdcNS, legacyNS, clipUMID; + // Used to Parse the Non-XMP /non real time metadata file associated with the clip ExpatAdapter * expat; XML_Node * clipMetadata; diff --git a/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp b/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp index fab0925..f455a8c 100644 --- a/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp +++ b/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp @@ -15,9 +15,11 @@ #include "XMPFiles/source/XMPFiles_Impl.hpp" #include "source/XMPFiles_IO.hpp" #include "source/XIO.hpp" +#include "source/IOUtils.hpp" #include "XMPFiles/source/FileHandlers/XDCAM_Handler.hpp" #include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" #include "third-party/zuid/interfaces/MD5.h" using namespace std; @@ -30,7 +32,7 @@ using namespace std; /// 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): +/// A typical FAM layout looks like (note mixed case for the nested folders): /// /// .../MyMovie/ /// INDEX.XML @@ -54,6 +56,51 @@ using namespace std; /// E0002E01.SMI /// E0002M01.XML /// +/// A typical FAM XMPilot layout looks like (note mixed case for the nested folders): +/// +/// .../MyMovie/ +/// DISCMETA.XML +/// MEDIAPRO.XML +/// General/ +/// Clip/ +/// Office_0001.MXF +/// Office_0001M01.XML +/// Office_0001M01.XMP +/// Office_0002.MXF +/// Office_0002M01.XML +/// Office_0002M01.XMP +/// Sub/ +/// Office_0001S01.MXF +/// Office_0002S01.MXF +/// Edit/ +/// UserData/ +/// unknown files +/// +/// A typical FAM XDCAM Memory SxS layout looks like (note mixed case for the nested folders): +/// +/// .../MyMovie/ +/// DISCMETA.XML +/// MEDIAPRO.XML +/// CUEUP.XML +/// General/ +/// Clip/ +/// C0001.MXF +/// C0001M01.XML +/// C0001M01.XMP +/// C0001R01.BIM +/// C0002.MXF +/// C0002M01.XML +/// C0002M01.XMP +/// C0001R01.BIM +/// Sub/ +/// C0001S01.MXF +/// C0002S01.MXF +/// Edit/ +/// Take/ +/// T0001.SMI +/// T0001M01.XML +/// UserData/ +/// /// A typical SAM layout looks like: /// /// .../MyMovie/ @@ -191,7 +238,10 @@ bool XDCAM_CheckFormat ( XMP_FileFormat format, rootPath += gpName; gpName.erase(); - if ( Host_IO::GetChildMode ( rootPath.c_str(), "ALIAS.XML" ) != Host_IO::kFMode_IsFile ) { + // XMPilot has no ALIAS.XML, but does have a UserData folder, don't change the first + // letter of the clip name for XMPilot. + if ( (Host_IO::GetChildMode ( rootPath.c_str(), "ALIAS.XML" ) != Host_IO::kFMode_IsFile) && + (Host_IO::GetChildMode ( rootPath.c_str(), "UserData" ) != Host_IO::kFMode_IsFolder) ) { clipName[0] = 'C'; // ! See notes above about pending bug. } @@ -232,7 +282,13 @@ bool XDCAM_CheckFormat ( XMP_FileFormat format, tempPath = rootPath; - if ( Host_IO::GetChildMode ( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile ) return false; + // XMPilot does not have INDEX.XML but does have UserData. + if ( (Host_IO::GetChildMode ( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile) && + !((Host_IO::GetChildMode ( rootPath.c_str(), "UserData" ) == Host_IO::kFMode_IsFolder) + // Changes introduced by Sony for XDCAM Memory SxS format in the FAM file structure are + // 1) There is no INDEX.XML in the root directory for XDCAM Memory SxS. + // 2) There is a new Take folder(similar to XDCAMEX) in the root directory. + || (Host_IO::GetChildMode ( tempPath.c_str(), "Take" ) == Host_IO::kFMode_IsFolder))) 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; @@ -391,6 +447,79 @@ XMPFileHandler * XDCAM_MetaHandlerCTor ( XMPFiles * parent ) } // XDCAM_MetaHandlerCTor + +// ================================================================================================= +// XDCAM_MetaHandler::SetSidecarPath +// ==================================== +void XDCAM_MetaHandler::SetSidecarPath() +{ + // Here, we set the appropriate sidecar name for this format. + // If, the format if XMPilot (no INDEX.XML but UserData folder present) or + // SxS (no INDEX.XML but Take folder present) then sidecar name will be + // old name used by MXFHandler i.e, {clipName}.MXF.xmp or {clipname}.mxf.xmp + // For all other cases, new side car name i.e, {clipname}M01.XMP will be used. + + try + { + if(this->isFAM && Host_IO::GetChildMode ( this->rootPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile && + (Host_IO::GetChildMode ( rootPath.c_str(), "UserData" ) == Host_IO::kFMode_IsFolder + || Host_IO::GetChildMode ( this->rootPath.c_str(), "Take" ) == Host_IO::kFMode_IsFolder) ) + { + // this is either XMPilot or SxS format. + XMP_VarString mxfFilePath; + if(MakeClipFilePath ( &mxfFilePath , ".MXF", true ) || MakeClipFilePath ( &mxfFilePath , ".mxf", true ) ) + { + Host_IO::FileRef hostRef = Host_IO::Open ( mxfFilePath.c_str(), Host_IO::openReadOnly ); + if ( hostRef != Host_IO::noFileRef ) + { + + XMPFiles_IO mxfFile ( hostRef, mxfFilePath.c_str() , Host_IO::openReadOnly ); + + if ( Host_IO::Length(hostRef) >= 16 ) + { + XMP_Uns8 buffer[16]; + Host_IO::Seek(hostRef, 0, kXMP_SeekFromStart); + XMP_Uns32 readBytes = Host_IO::Read(hostRef, buffer, 16 ); + + if ( ( readBytes == 16 ) && + ( GetUns32BE(&buffer[0]) == 0x060E2B34 ) && + ( GetUns32BE(&buffer[4]) == 0x02050101 ) && + ( GetUns32BE(&buffer[8]) == 0x0D010201 ) && + ( ( GetUns32BE(&buffer[12]) & 0xFFFF00FF ) == 0x01020000 ) + ) + { + // If cached MXF file name is present then use it otherwise + // side car generated on case insensitive OS may not be read on case sensitive OS. + // For example, if file name is X.MXF then windows says X.mxf is same as X.MXF so + // we may land up generating side car name as X.mxf.xmp which will not be read on + // Mac which will search specifically for X.MXF.xmp + XMP_VarString filePath = this->parent->GetFilePath(); + XMP_VarString ext; + XIO::SplitFileExtension(&filePath, &ext); + if(ext == "MXF" || ext == "mxf") + { + this->sidecarPath = this->parent->GetFilePath() + ".xmp"; + } + else + { + this->sidecarPath = mxfFilePath + ".xmp"; + } + } + } + } + } + } + } + catch( ... ) + { + // Use new side car name. + } + if(this->sidecarPath.empty()) + { + MakeClipFilePath ( &this->sidecarPath , "M01.XMP", false ) ; + } +}// XDCAM_MetaHandler::SetSidecarPath + // ================================================================================================= // XDCAM_MetaHandler::XDCAM_MetaHandler // ==================================== @@ -406,7 +535,7 @@ XDCAM_MetaHandler::XDCAM_MetaHandler ( XMPFiles * _parent ) : isFAM(false), expa if ( this->parent->tempPtr == 0 ) { // The CheckFormat call might have been skipped. - this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath ); + this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() ); } this->rootPath.assign ( (char*) this->parent->tempPtr ); @@ -419,6 +548,9 @@ XDCAM_MetaHandler::XDCAM_MetaHandler ( XMPFiles * _parent ) : isFAM(false), expa XIO::SplitLeafName ( &this->rootPath, &temp ); XMP_Assert ( (temp == "FAM") || (temp == "SAM") ); if ( temp == "FAM" ) this->isFAM = true; + // backward compatibility ensured for XMPilot Clips + // XMPilot is FAM + this->SetSidecarPath(); XMP_Assert ( this->isFAM ? (this->parent->format == kXMP_XDCAM_FAMFile) : (this->parent->format == kXMP_XDCAM_SAMFile) ); } // XDCAM_MetaHandler::XDCAM_MetaHandler @@ -551,6 +683,26 @@ void XDCAM_MetaHandler::CleanupLegacyXML() } // XDCAM_MetaHandler::CleanupLegacyXML +void XDCAM_MetaHandler::readXMLFile( XMP_StringPtr filePath, ExpatAdapter* &expat ) +{ + Host_IO::FileRef hostRef = Host_IO::Open ( filePath, Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return; // The open failed. + XMPFiles_IO xmlFile ( hostRef, filePath, Host_IO::openReadOnly ); + + expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); + if ( 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; + expat->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + expat->ParseBuffer ( 0, 0, true ); // End the parse. + + xmlFile.Close(); +} + // ================================================================================================= // XDCAM_MetaHandler::GetFileModDate // ================================= @@ -607,6 +759,660 @@ bool XDCAM_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) } // XDCAM_MetaHandler::GetFileModDate + +// ================================================================================================= +// XDCAM_MetaHandler::GetClipUmid +// ============================== +bool XDCAM_MetaHandler::GetClipUmid ( std::string &clipUmid ) +{ + std::string clipInfoPath; + ExpatAdapter* clipInfoExpat = 0 ; + bool umidFound = false; + XMP_StringPtr nameSpace = 0; + try { + this->MakeClipFilePath ( &clipInfoPath, "C01.SMI" ) ; + readXMLFile( clipInfoPath.c_str(), clipInfoExpat ); + if ( clipInfoExpat != 0 ) + { + XML_Node & xmlTree = clipInfoExpat->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 ) + { + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( XMP_LitMatch ( rootLocalName, "smil" ) ) + { + XMP_StringPtr umidValue = rootElem->GetAttrValue ( "umid" ); + if ( umidValue != 0 ) { + clipUmid = umidValue; + umidFound = true; + } + } + } + } + if( ! umidFound ) + { //try to get the umid from the NRT metadata + delete ( clipInfoExpat ) ; clipInfoExpat = 0; + this->MakeClipFilePath ( &clipInfoPath, "M01.XML" ) ; + readXMLFile( clipInfoPath.c_str(), clipInfoExpat ) ; + if ( clipInfoExpat != 0 ) + { + XML_Node & xmlTree = clipInfoExpat->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 ) + { + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) + { + nameSpace = rootElem->ns.c_str() ; + XML_NodePtr targetProp = rootElem->GetNamedElement ( nameSpace, "TargetMaterial" ); + if ( (targetProp != 0) && targetProp->IsEmptyLeafNode() ) { + XMP_StringPtr umidValue = targetProp->GetAttrValue ( "umidRef" ); + if ( umidValue != 0 ) { + clipUmid = umidValue; + umidFound = true; + } + } + } + } + } + } + } catch ( ... ) { + } + delete ( clipInfoExpat ) ; + return umidFound; +}// XDCAM_MetaHandler::GetClipUmid + +// ================================================================================================= +// XDCAM_MetaHandler::IsClipsPlanning +// ================================== +bool XDCAM_MetaHandler::IsClipsPlanning ( std::string clipUmid , XMP_StringPtr planPath ) +{ + ExpatAdapter* planniingExpat = 0 ; + XMP_StringPtr nameSpace = 0 ; + try { + readXMLFile( planPath, planniingExpat ); + if ( planniingExpat != 0 ) + { + XML_Node & xmlTree = planniingExpat->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 ) + { + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( XMP_LitMatch ( rootLocalName, "PlanningMetadata" ) ) + { + nameSpace = rootElem->ns.c_str() ; + size_t noOfMaterialGroups = rootElem->CountNamedElements ( nameSpace, "MaterialGroup" ) ; + while( noOfMaterialGroups-- ) + { + XML_NodePtr mgNode = rootElem->GetNamedElement( nameSpace, "MaterialGroup" ); + size_t noOfMaterialElements = mgNode->CountNamedElements ( nameSpace, "Material" ) ; + while( noOfMaterialElements-- ) + { + XML_NodePtr materialNode = mgNode->GetNamedElement( nameSpace, "Material" ); + XMP_StringPtr materialType = materialNode->GetAttrValue ( "type" ); + if ( XMP_LitMatch( materialType , "clip" ) ) + { + XMP_StringPtr umidValue = materialNode->GetAttrValue ( "umidRef" ); + if ( umidValue != 0 && XMP_LitMatch( umidValue , clipUmid.c_str() ) ) + { + delete ( planniingExpat ) ; + return true; + } + } + + } + } + } + } + } + + } catch ( ... ) { + } + delete ( planniingExpat ) ; + return false; +} // XDCAM_MetaHandler::IsClipsPlanning + + +// ================================================================================================= +// XDCAM_MetaHandler::RefersClipUmid +// ================================== +bool XDCAM_MetaHandler::RefersClipUmid ( std::string clipUmid , XMP_StringPtr editInfoPath ) +{ + ExpatAdapter* editInfoExpat = 0 ; + XMP_StringPtr nameSpace = 0 ; + try { + readXMLFile( editInfoPath, editInfoExpat ); + if ( editInfoExpat != 0 ) + { + XML_Node & xmlTree = editInfoExpat->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 ) + { + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( XMP_LitMatch ( rootLocalName, "smil" ) ) + { + nameSpace = rootElem->ns.c_str() ; + size_t noOfBodyElements = rootElem->CountNamedElements ( nameSpace, "body" ) ; + while( noOfBodyElements-- ) + { + XML_NodePtr bodyNode = rootElem->GetNamedElement( nameSpace, "body" ); + size_t noOfParElements = bodyNode->CountNamedElements ( nameSpace, "par" ) ; + while( noOfParElements-- ) + { + XML_NodePtr parNode = bodyNode->GetNamedElement( nameSpace, "par" ); + size_t noOfRefElements = parNode->CountNamedElements ( nameSpace, "ref" ) ; + size_t whichElem = 0; + while( noOfRefElements-- ) + { + XML_NodePtr refNode = parNode->GetNamedElement( nameSpace, "ref" ,whichElem++ ); + XMP_StringPtr umidValue = refNode->GetAttrValue ( "src" ); + if ( umidValue != 0 && + ( XMP_LitMatch( umidValue , clipUmid.c_str() ) || + ( strlen(umidValue) > 15 && XMP_LitMatch( &umidValue[15] , clipUmid.c_str() ) ) + ) + ) + { + delete ( editInfoExpat ) ; + return true; + } + } + } + } + } + } + } + + } catch ( ... ) { + } + delete ( editInfoExpat ) ; + return false; +} // XDCAM_MetaHandler::RefersClipUmid + +inline bool IsDigit( char c ) +{ + return c >= '0' && c <= '9'; +} + + +// ================================================================================================= +// XDCAM_MetaHandler::GetEditInfoFilesSAM +// ====================================== +bool XDCAM_MetaHandler::GetEditInfoFilesSAM ( std::vector<std::string> &editInfoList ) +{ + std::string clipUmid; + bool found = false; + + if( GetClipUmid ( clipUmid ) ) + { + std::string editFolderPath = this->rootPath + kDirChar + "PROAV" + kDirChar + "EDTR" + kDirChar ; + if ( Host_IO::Exists( editFolderPath.c_str() ) && + Host_IO::GetFileMode( editFolderPath.c_str() ) == Host_IO::kFMode_IsFolder + ) + { + Host_IO::AutoFolder edtrFolder, editFolder; + std::string edtrChildName, edlistChild; + + edtrFolder.folder = Host_IO::OpenFolder ( editFolderPath.c_str() ); + while ( Host_IO::GetNextChild ( edtrFolder.folder, &edtrChildName ) ) { + size_t childLen = edtrChildName.size(); + std::string editListFolderPath = editFolderPath + edtrChildName + kDirChar ; + if ( ! ( childLen == 5 && + edtrChildName[0] == 'E' && + IsDigit( edtrChildName[1] ) && + IsDigit( edtrChildName[2] ) && + IsDigit( edtrChildName[3] ) && + IsDigit( edtrChildName[4] ) && + Host_IO::GetFileMode( editListFolderPath.c_str() ) == Host_IO::kFMode_IsFolder + ) ) continue; + + editFolder.folder = Host_IO::OpenFolder ( editListFolderPath.c_str() ); + while ( Host_IO::GetNextChild ( editFolder.folder, &edlistChild ) ) { + size_t filenamelen = edlistChild.size(); + std::string editListFilePath = editListFolderPath + edlistChild ; + if ( ! ( filenamelen == 12 && + edlistChild.compare ( filenamelen - 4, 4 , ".SMI" ) == 0 && + edlistChild.compare ( 0, edtrChildName.size(), edtrChildName ) == 0 && + Host_IO::GetFileMode( editListFilePath.c_str() ) == Host_IO::kFMode_IsFile + ) ) continue; + if( RefersClipUmid ( clipUmid , editListFilePath.c_str() ) ) + { + found = true ; + editInfoList.push_back( editListFilePath ); + } + } + } + } + } + return found; +} // XDCAM_MetaHandler::GetEditInfoFilesSAM + +// ================================================================================================= +// XDCAM_MetaHandler::GetInfoFilesFAM +// ================================== +bool XDCAM_MetaHandler::GetInfoFilesFAM ( std::vector<std::string> &editInfoList, std::string pathToFolder) +{ + std::string clipUmid; + bool found = false; + + if( GetClipUmid ( clipUmid ) ) + { + if ( Host_IO::Exists( pathToFolder.c_str() ) && + Host_IO::GetFileMode( pathToFolder.c_str() ) == Host_IO::kFMode_IsFolder + ) + { + Host_IO::AutoFolder editFolder; + std::string edlistChild; + + editFolder.folder = Host_IO::OpenFolder ( pathToFolder.c_str() ); + while ( Host_IO::GetNextChild ( editFolder.folder, &edlistChild ) ) { + size_t filenamelen = edlistChild.size(); + std::string editListFilePath = pathToFolder + edlistChild ; + if ( ! ( filenamelen > 7 && + edlistChild.compare ( filenamelen - 4, 4 , ".SMI" ) == 0 && + Host_IO::GetFileMode( editListFilePath.c_str() ) == Host_IO::kFMode_IsFile + ) ) continue; + if( RefersClipUmid ( clipUmid , editListFilePath.c_str() ) ) + { + found = true ; + editInfoList.push_back( editListFilePath ); + } + } + } + } + return found; +} // XDCAM_MetaHandler::GetInfoFilesFAM + +// ================================================================================================= +// XDCAM_MetaHandler::GetPlanningFilesFAM +// ====================================== +bool XDCAM_MetaHandler::GetPlanningFilesFAM ( std::vector<std::string> &planInfoList, std::string pathToFolder) +{ + std::string clipUmid; + bool found = false; + + if( GetClipUmid ( clipUmid ) ) + { + if ( Host_IO::Exists( pathToFolder.c_str() ) && + Host_IO::GetFileMode( pathToFolder.c_str() ) == Host_IO::kFMode_IsFolder + ) + { + Host_IO::AutoFolder planFolder; + std::string listChild; + + planFolder.folder = Host_IO::OpenFolder ( pathToFolder.c_str() ); + while ( Host_IO::GetNextChild ( planFolder.folder, &listChild ) ) { + size_t filenamelen = listChild.size(); + std::string listFilePath = pathToFolder + listChild ; + if ( ! ( filenamelen > 4 && + ( listChild.compare ( filenamelen - 4, 4 , ".XML" ) == 0 + || + listChild.compare ( filenamelen - 4, 4 , ".xml" ) == 0 + ) + && + Host_IO::GetFileMode( listFilePath.c_str() ) == Host_IO::kFMode_IsFile + ) ) continue; + if( IsClipsPlanning ( clipUmid , listFilePath.c_str() ) ) + { + found = true ; + planInfoList.push_back( listFilePath ); + } + } + } + } + return found; +} // XDCAM_MetaHandler::GetPlanningFilesFAM + +// ================================================================================================= +// XDCAM_MetaHandler::IsMetadataWritable +// ======================================= + +bool XDCAM_MetaHandler::IsMetadataWritable ( ) +{ + std::vector<std::string> metadataFiles; + FillMetadataFiles(&metadataFiles); + std::vector<std::string>::iterator itr = metadataFiles.begin(); + // Check whether sidecar is writable, if not then check if it can be created. + bool xmpWritable = Host_IO::Writable( itr->c_str(), true ); + // Check for legacy metadata file. + bool xmlWritable = Host_IO::Writable( (++itr)->c_str(), false ); + return ( xmlWritable && xmpWritable ); +}// XDCAM_MetaHandler::IsMetadataWritable + +// ================================================================================================= +// XDCAM_MetaHandler::FillFAMAssociatedResources +// ============================================= +void XDCAM_MetaHandler::FillFAMAssociatedResources ( std::vector<std::string> * resourceList ) +{ + // The possible associated resources: + // .../MyMovie/ + // ALIAS.XML + // INDEX.XML + // DISCMETA.XML + // MEDIAPRO.XML + // MEDIAPRO.BUP + // CUEUP.XML + // CUEUP.BUP + // Clip/ + // AAAAA.MXF AAAAA is the clipname with clipserial + // XX is a counter which will start from from 01 and can go upto 99 based + // on number of files present in this folder with same extension and same clipname/editListName/Takename. + // AAAAAMXX.XML + // AAAAAMXX.XMP + // AAAAARXX.BIM + // Sub/ + // AAAAASXX.MXF + // Local/ + // AAAAACXX.SMI + // AAAAACXX.PPN + // Edit/ DDDDD is the editListName + // DDDDDEXX.SMI + // DDDDDMXX.XML + // Take/ TTTTT is the Takename + // TTTTT.SMI + // TTTTTUNN.SMI NN is a counter which goes from 01 to N-1 where N is number of media, this + // take is divided into. For Nth, TTTTT.SMI shall be picked up. + // TTTTTMXX.XML + // General/ + // Sony/ + // Planning/ AAAAA is the clipname without clipserial + // YYYYMMDDHHMISS is DateTime + // BBBBB_YYYYMMDDHHMISS.xml + // UserData/ + // + + //Add RootPath + std::string filePath = rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists( resourceList, filePath ); + + // Get the files present directly inside root folder. + filePath = rootPath + kDirChar + "ALIAS.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + filePath = rootPath + kDirChar + "INDEX.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + filePath = rootPath + kDirChar + "DISCMETA.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + filePath = rootPath + kDirChar + "MEDIAPRO.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + filePath = rootPath + kDirChar + "MEDIAPRO.BUP"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + filePath = rootPath + kDirChar + "CUEUP.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + filePath = rootPath + kDirChar + "CUEUP.BUP"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + // Add the UserData folder which is used to identify the format in any way + filePath = rootPath + kDirChar + "UserData" + kDirChar; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + XMP_VarString clipPath = rootPath + kDirChar + "Clip" + kDirChar ; + + size_t oldCount = resourceList->size(); + // Get the files present inside clip folder. + XMP_VarString regExp; + XMP_StringVector regExpVec; + + regExp = "^" + clipName + ".MXF$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "M\\d\\d.XML$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "R\\d\\d.BIM$"; + regExpVec.push_back ( regExp ); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExpVec, false, true, true ); + PackageFormat_Support::AddResourceIfExists(resourceList, this->sidecarPath); + if ( resourceList->size() <= oldCount ) + { + PackageFormat_Support::AddResourceIfExists(resourceList, clipPath); + } + + //Get the files Under Sub folder + clipPath = rootPath + kDirChar + "Sub" + kDirChar ; + regExpVec.clear(); + regExp = "^" + clipName + "S\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + oldCount = resourceList->size(); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExpVec, false, true, true ); + // Add Sub folder if no file inside this, was added. + if ( resourceList->size() <= oldCount ) + { + PackageFormat_Support::AddResourceIfExists(resourceList, clipPath); + } + + //Get the files Under Local folder + clipPath = rootPath + kDirChar + "Local" + kDirChar ; + regExpVec.clear(); + regExp = "^" + clipName + "C\\d\\d.SMI$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "I\\d\\d.PPN$"; + regExpVec.push_back ( regExp ); + oldCount = resourceList->size(); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExpVec, false, true, true ); + + //Add the Edit lists associated to this clip + XMP_StringVector editInfoList; + bool atLeastOneFileAdded = false; + clipPath = rootPath + kDirChar + "Edit" + kDirChar ; + if ( GetInfoFilesFAM ( editInfoList , clipPath ) ) + { + size_t noOfEditInfoFiles = editInfoList.size() ; + for( size_t count = 0; count < noOfEditInfoFiles; count++ ) + { + atLeastOneFileAdded = PackageFormat_Support::AddResourceIfExists(resourceList, editInfoList[count]) ? true : atLeastOneFileAdded; + std::string editNRTFile = editInfoList[count] ; + size_t filenamelen = editInfoList[count].length() ; + editNRTFile[ filenamelen - 7 ] = 'M'; + editNRTFile[ filenamelen - 3 ] = 'X'; + editNRTFile[ filenamelen - 2 ] = 'M'; + editNRTFile[ filenamelen - 1 ] = 'L'; + atLeastOneFileAdded = PackageFormat_Support::AddResourceIfExists(resourceList, editNRTFile ) ? true : atLeastOneFileAdded; + } + } + // Add Edit folder if no file inside this, was added. + if ( !atLeastOneFileAdded ) + { + PackageFormat_Support::AddResourceIfExists(resourceList, clipPath); + } + + atLeastOneFileAdded = false; + + //Add the Takes associated to this clip + XMP_StringVector takeList; + clipPath = rootPath + kDirChar + "Take" + kDirChar ; + if( GetInfoFilesFAM ( takeList , clipPath ) ) + { + size_t noOfTakes = takeList.size() ; + for( size_t count = 0; count < noOfTakes; count++ ) + { + atLeastOneFileAdded = PackageFormat_Support::AddResourceIfExists(resourceList, takeList[count]) ? true : atLeastOneFileAdded; + XMP_VarString takeNRTFile = takeList[count] ; + size_t filenamelen = takeList[count].length() ; + if ( takeNRTFile[ filenamelen - 7 ] == 'U' + && IsDigit( takeNRTFile[ filenamelen - 6 ] ) + && IsDigit( takeNRTFile[ filenamelen - 5 ] ) ) + { + takeNRTFile.erase( takeNRTFile.begin() + filenamelen - 7, takeNRTFile.end() ) ; + } + else + { + takeNRTFile.erase( takeNRTFile.begin() + filenamelen - 4, takeNRTFile.end() ) ; + } + + XMP_VarString fileName; + size_t pos = takeNRTFile.find_last_of ( kDirChar ); + fileName = takeNRTFile.substr ( pos + 1 ); + XMP_VarString regExp = "^" + fileName + "M\\d\\d.XML$"; + oldCount = resourceList->size(); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExp, false, true, true ); + atLeastOneFileAdded = resourceList->size() > oldCount; + } + } + // Add Take folder if no file inside this, was added. + if(!atLeastOneFileAdded) + { + filePath = rootPath + kDirChar + "Take" + kDirChar; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + } + + //Add the Planning Metadata Files associated to this clip + XMP_StringVector planList; + clipPath = rootPath + kDirChar + "General" + kDirChar + "Sony" + kDirChar+ "Planning" + kDirChar; + if( GetPlanningFilesFAM ( planList , clipPath ) ) + { + size_t noOfPlans = planList.size() ; + for( size_t count = 0; count < noOfPlans; count++ ) + { + resourceList->push_back( planList[count] ); + } + } +} // XDCAM_MetaHandler::FillFAMAssociatedResources + +// ================================================================================================= +// XDCAM_MetaHandler::FillSAMAssociatedResources +// ============================================= +void XDCAM_MetaHandler::FillSAMAssociatedResources ( std::vector<std::string> * resourceList ) +{ + // The possible associated resources: + // .../MyMovie/ + // PROAV/ + // INDEX.XML + // INDEX.BUP + // DISCMETA.XML + // DISCINFO.XML + // DISCINFO.BUP + // CLPR/ + // CXXXX/ XXXX is ClipSerial and NN is a counter which will start from from 01 and can go upto 99 based + // on number of files present in this folder with same extension. + // CXXXXCNN.SMI + // CXXXXVNN.MXF + // CXXXXANN.MXF + // CXXXXRNN.BIM + // CXXXXINN.PPN + // CXXXXMNN.XML + // CXXXXSNN.MXF + // EDTR/ + // EXXXX: + // EXXXXENN.SMI + // EXXXXMNN.XML + // + std::string proavPath = rootPath + kDirChar + "PROAV" + kDirChar; + std::string filePath; + //Add RootPath + filePath = rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists( resourceList, filePath ); + + // Get the files present directly inside PROAV folder. + filePath = proavPath + "INDEX.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + filePath = proavPath + "INDEX.BUP"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + filePath = proavPath + "DISCINFO.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + filePath = proavPath + "DISCINFO.BUP"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + filePath = proavPath + "DISCMETA.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + XMP_VarString clipPath = proavPath + "CLPR" + kDirChar + clipName + kDirChar; + XMP_VarString regExp; + XMP_StringVector regExpVec; + + regExp = "^" + clipName + "C\\d\\d.SMI$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "M\\d\\d.XML$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "V\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "A\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "R\\d\\d.BIM$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "I\\d\\d.PPN$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "S\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExpVec, false, true, true ); + PackageFormat_Support::AddResourceIfExists(resourceList, this->sidecarPath); + //Add the Edit lists that refer this clip + std::vector<std::string> editInfoList; + if( GetEditInfoFilesSAM ( editInfoList ) ) + { + size_t noOfEditInfoFiles = editInfoList.size() ; + for( size_t count = 0; count < noOfEditInfoFiles; count++ ) + { + PackageFormat_Support::AddResourceIfExists(resourceList, editInfoList[count]); + std::string editNRTFile = editInfoList[count].c_str() ; + size_t filenamelen = editInfoList[count].length() ; + editNRTFile[ filenamelen - 7 ] = 'M'; + editNRTFile[ filenamelen - 3 ] = 'X'; + editNRTFile[ filenamelen - 2 ] = 'M'; + editNRTFile[ filenamelen - 1 ] = 'L'; + PackageFormat_Support::AddResourceIfExists(resourceList, editNRTFile ); + } + } +}// XDCAM_MetaHandler::FillSAMAssociatedResources + +// ================================================================================================= +// XDCAM_MetaHandler::FillAssociatedResources +// ====================================== +void XDCAM_MetaHandler::FillAssociatedResources ( std::vector<std::string> * resourceList ) +{ + if( this->isFAM ) + FillFAMAssociatedResources ( resourceList ); + else + FillSAMAssociatedResources ( resourceList ); +} +// ================================================================================================= +// XDCAM_MetaHandler::FillMetadataFiles +// ==================================== +void XDCAM_MetaHandler::FillMetadataFiles ( std::vector<std::string> * metadataFiles ) +{ + std::string noExtPath, filePath; + + if(this->isFAM) { + noExtPath = rootPath + kDirChar + "Clip" + kDirChar + clipName; + } else { + noExtPath = rootPath + kDirChar + "PROAV" + kDirChar + "CLPR" + + kDirChar + clipName + kDirChar + clipName; + } + + metadataFiles->push_back ( this->sidecarPath ); + filePath = noExtPath + "M01.XML"; + metadataFiles->push_back ( filePath ); + +} // XDCAM_MetaHandler::FillMetadataFiles + // ================================================================================================= // XDCAM_MetaHandler::CacheFileData // ================================ @@ -621,17 +1427,16 @@ void XDCAM_MetaHandler::CacheFileData() // 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. + if ( ! Host_IO::Exists ( this->sidecarPath.c_str() ) ) return; // No XMP. - // Read the entire .XMP file. + // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 + // only if the file does not exist. 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. + XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( this->sidecarPath.c_str(), readOnly ); + if ( xmpFile == 0 ) XMP_Throw ( "XDCAM XMP file open failure", kXMPErr_InternalFailure ); this->parent->ioRef = xmpFile; XMP_Int64 xmpLen = xmpFile->Length(); @@ -697,22 +1502,8 @@ void XDCAM_MetaHandler::ProcessXMP() 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(); + readXMLFile( xmlPath.c_str(),this->expat ); + if ( this->expat == 0 ) return; // The root element should be NonRealTimeMeta in some namespace. Take whatever this file uses. @@ -785,14 +1576,13 @@ void XDCAM_MetaHandler::UpdateFile ( bool doSafeUpdate ) // ----------------------------------------------------------------------- // 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() ); + bool haveXMP = Host_IO::Exists ( this->sidecarPath.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 ); + 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 XDCAM XMP file", kXMPErr_ExternalFailure ); } diff --git a/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp b/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp index fbcc8bb..f03ada8 100644 --- a/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp +++ b/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp @@ -50,6 +50,10 @@ public: bool GetFileModDate ( XMP_DateTime * modDate ); + void FillMetadataFiles ( std::vector<std::string> * metadataFiles ); + void FillAssociatedResources ( std::vector<std::string> * resourceList ); + bool IsMetadataWritable ( ) ; + void CacheFileData(); void ProcessXMP(); @@ -70,10 +74,21 @@ private: bool MakeMediaproPath ( std::string * path, bool checkFile = false ); void MakeLegacyDigest ( std::string * digestStr ); void CleanupLegacyXML(); + void SetSidecarPath(); + + void readXMLFile( XMP_StringPtr filePath,ExpatAdapter* &expat ); + bool GetClipUmid ( std::string &clipUmid ) ; + bool IsClipsPlanning ( std::string clipUmid , XMP_StringPtr planPath ) ; + bool RefersClipUmid ( std::string clipUmid , XMP_StringPtr editInfoPath ) ; + bool GetInfoFilesFAM ( std::vector<std::string> &InfoList, std::string pathToFolder) ; + bool GetPlanningFilesFAM ( std::vector<std::string> &planInfoList, std::string pathToFolder) ; + bool GetEditInfoFilesSAM ( std::vector<std::string> &editInfoList ) ; + void FillFAMAssociatedResources ( std::vector<std::string> * resourceList ); + void FillSAMAssociatedResources ( std::vector<std::string> * resourceList ); bool GetMediaProMetadata ( SXMPMeta * xmpObjPtr, const std::string& clipUMID, bool digestFound ); - std::string rootPath, clipName, xdcNS, legacyNS; + std::string rootPath, clipName, xdcNS, legacyNS, sidecarPath; bool isFAM; diff --git a/XMPFiles/source/FormatSupport/ASF_Support.cpp b/XMPFiles/source/FormatSupport/ASF_Support.cpp index 35adce6..709aea5 100644 --- a/XMPFiles/source/FormatSupport/ASF_Support.cpp +++ b/XMPFiles/source/FormatSupport/ASF_Support.cpp @@ -30,11 +30,11 @@ int IsEqualGUID ( const GUID& guid1, const GUID& guid2 ) } #endif -ASF_Support::ASF_Support() : legacyManager(0), posFileSizeInfo(0) {} +ASF_Support::ASF_Support() : legacyManager(0),progressTracker(0), posFileSizeInfo(0) {} -ASF_Support::ASF_Support ( ASF_LegacyManager* _legacyManager ) : posFileSizeInfo(0) +ASF_Support::ASF_Support ( ASF_LegacyManager* _legacyManager,XMP_ProgressTracker* _progressTracker ) + :legacyManager(_legacyManager),progressTracker(_progressTracker), posFileSizeInfo(0) { - legacyManager = _legacyManager; } ASF_Support::~ASF_Support() @@ -645,7 +645,11 @@ bool ASF_Support::WriteHeaderObject ( XMP_IO* sourceRef, XMP_IO* destRef, const // if we are operating on the same file (in-place update), place pointer before writing if ( sourceRef == destRef ) destRef->Seek ( object.pos, kXMP_SeekFromStart ); - + if ( this->progressTracker != 0 ) + { + XMP_Assert ( this->progressTracker->WorkInProgress() ); + this->progressTracker->AddTotalWork ( (float)header.size() ); + } // write header destRef->Write ( header.c_str(), header.size() ); @@ -1139,7 +1143,7 @@ void ASF_LegacyManager::ImportLegacy ( SXMPMeta* xmp ) 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 ( ! fields[fieldCopyrightURL].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 ); diff --git a/XMPFiles/source/FormatSupport/ASF_Support.hpp b/XMPFiles/source/FormatSupport/ASF_Support.hpp index 9d9060b..3e170e3 100644 --- a/XMPFiles/source/FormatSupport/ASF_Support.hpp +++ b/XMPFiles/source/FormatSupport/ASF_Support.hpp @@ -16,6 +16,7 @@ #include "XMPFiles/source/XMPFiles_Impl.hpp" #include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" #include "source/XIO.hpp" +#include "source/XMP_ProgressTracker.hpp" // currently exclude LicenseURL from reconciliation #define Exclude_LicenseURL_Recon 1 @@ -187,7 +188,7 @@ public: }; ASF_Support(); - ASF_Support ( ASF_LegacyManager* legacyManager ); + ASF_Support ( ASF_LegacyManager* legacyManager, XMP_ProgressTracker* _progressTracker); virtual ~ASF_Support(); long OpenASF ( XMP_IO* fileRef, ObjectState & inOutObjectState ); @@ -215,6 +216,7 @@ public: private: ASF_LegacyManager* legacyManager; + XMP_ProgressTracker* progressTracker;//not owned by ASF_Support XMP_Uns64 posFileSizeInfo; static std::string ReplaceString ( std::string& operand, std::string& str, int offset, int count ); diff --git a/XMPFiles/source/FormatSupport/ID3_Support.cpp b/XMPFiles/source/FormatSupport/ID3_Support.cpp index 2bc9a1f..e33e39e 100644 --- a/XMPFiles/source/FormatSupport/ID3_Support.cpp +++ b/XMPFiles/source/FormatSupport/ID3_Support.cpp @@ -18,32 +18,370 @@ #include <vector> -// ================================================================================================= - #define MIN(a,b) ((a) < (b) ? (a) : (b)) +namespace ID3_Support { + +// ================================================================================================= + 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". +static size_t numberedGenreCount = 0; // Set in InitializeGlobals, used in ID3v1Tag::read and write. + +struct GenreInfo { const char * code; const char * name; }; + +static const GenreInfo kAbbreviatedGenres[] = { // ID3 v3 or v4 genre abbreviations. + { "RX", "Remix" }, + { "CR", "Cover" }, + { 0, 0 } +}; + +static const GenreInfo kNumberedGenres[] = { // Numeric genre codes from ID3 v1, complete range of 0..125. + { "0", "Blues" }, + { "1", "Classic Rock" }, + { "2", "Country" }, + { "3", "Dance" }, + { "4", "Disco" }, + { "5", "Funk" }, + { "6", "Grunge" }, + { "7", "Hip-Hop" }, + { "8", "Jazz" }, + { "9", "Metal" }, + { "10", "New Age" }, + { "11", "Oldies" }, + { "12", "Other" }, + { "13", "Pop" }, + { "14", "R&B" }, + { "15", "Rap" }, + { "16", "Reggae" }, + { "17", "Rock" }, + { "18", "Techno" }, + { "19", "Industrial" }, + { "20", "Alternative" }, + { "21", "Ska" }, + { "22", "Death Metal" }, + { "23", "Pranks" }, + { "24", "Soundtrack" }, + { "25", "Euro-Techno" }, + { "26", "Ambient" }, + { "27", "Trip-Hop" }, + { "28", "Vocal" }, + { "29", "Jazz+Funk" }, + { "30", "Fusion" }, + { "31", "Trance" }, + { "32", "Classical" }, + { "33", "Instrumental" }, + { "34", "Acid" }, + { "35", "House" }, + { "36", "Game" }, + { "37", "Sound Clip" }, + { "38", "Gospel" }, + { "39", "Noise" }, + { "40", "AlternRock" }, + { "41", "Bass" }, + { "42", "Soul" }, + { "43", "Punk" }, + { "44", "Space" }, + { "45", "Meditative" }, + { "46", "Instrumental Pop" }, + { "47", "Instrumental Rock" }, + { "48", "Ethnic" }, + { "49", "Gothic" }, + { "50", "Darkwave" }, + { "51", "Techno-Industrial" }, + { "52", "Electronic" }, + { "53", "Pop-Folk" }, + { "54", "Eurodance" }, + { "55", "Dream" }, + { "56", "Southern Rock" }, + { "57", "Comedy" }, + { "58", "Cult" }, + { "59", "Gangsta" }, + { "60", "Top 40" }, + { "61", "Christian Rap" }, + { "62", "Pop/Funk" }, + { "63", "Jungle" }, + { "64", "Native American" }, + { "65", "Cabaret" }, + { "66", "New Wave" }, + { "67", "Psychadelic" }, + { "68", "Rave" }, + { "69", "Showtunes" }, + { "70", "Trailer" }, + { "71", "Lo-Fi" }, + { "72", "Tribal" }, + { "73", "Acid Punk" }, + { "74", "Acid Jazz" }, + { "75", "Polka" }, + { "76", "Retro" }, + { "77", "Musical" }, + { "78", "Rock & Roll" }, + { "79", "Hard Rock" }, + { "80", "Folk" }, + { "81", "Folk-Rock" }, + { "82", "National Folk" }, + { "83", "Swing" }, + { "84", "Fast Fusion" }, + { "85", "Bebob" }, + { "86", "Latin" }, + { "87", "Revival" }, + { "88", "Celtic" }, + { "89", "Bluegrass" }, + { "90", "Avantgarde" }, + { "91", "Gothic Rock" }, + { "92", "Progressive Rock" }, + { "93", "Psychedelic Rock" }, + { "94", "Symphonic Rock" }, + { "95", "Slow Rock" }, + { "96", "Big Band" }, + { "97", "Chorus" }, + { "98", "Easy Listening" }, + { "99", "Acoustic" }, + { "100", "Humour" }, + { "101", "Speech" }, + { "102", "Chanson" }, + { "103", "Opera" }, + { "104", "Chamber Music" }, + { "105", "Sonata" }, + { "106", "Symphony" }, + { "107", "Booty Bass" }, + { "108", "Primus" }, + { "109", "Porn Groove" }, + { "110", "Satire" }, + { "111", "Slow Jam" }, + { "112", "Club" }, + { "113", "Tango" }, + { "114", "Samba" }, + { "115", "Folklore" }, + { "116", "Ballad" }, + { "117", "Power Ballad" }, + { "118", "Rhythmic Soul" }, + { "119", "Freestyle" }, + { "120", "Duet" }, + { "121", "Punk Rock" }, + { "122", "Drum Solo" }, + { "123", "A capella" }, // ! Should be Acapella, keep space for compatibility with old code. + { "124", "Euro-House" }, + { "125", "Dance Hall" }, + { 0, 0 } +}; + // ================================================================================================= -bool ID3_Support::InitializeGlobals() +bool InitializeGlobals() { + + kMapID3GenreCodeToName = new ID3GenreMap; + if ( kMapID3GenreCodeToName == 0 ) return false; + kMapID3GenreNameToCode = new ID3GenreMap; + if ( kMapID3GenreNameToCode == 0 ) return false; + + ID3GenreMap::value_type newValue; + + size_t i; + + for ( i = 0; kNumberedGenres[i].code != 0; ++i ) { + XMP_Assert ( (long)i == strtol ( kNumberedGenres[i].code, 0, 10 ) ); + ID3GenreMap::value_type code2Name ( kNumberedGenres[i].code, kNumberedGenres[i].name ); + kMapID3GenreCodeToName->insert ( kMapID3GenreCodeToName->end(), code2Name ); + ID3GenreMap::value_type name2Code ( kNumberedGenres[i].name, kNumberedGenres[i].code ); + kMapID3GenreNameToCode->insert ( kMapID3GenreNameToCode->end(), name2Code ); + } + + numberedGenreCount = i; // Used in ID3v1Tag::read and write. + + for ( i = 0; kAbbreviatedGenres[i].code != 0; ++i ) { + ID3GenreMap::value_type code2Name ( kAbbreviatedGenres[i].code, kAbbreviatedGenres[i].name ); + kMapID3GenreCodeToName->insert ( kMapID3GenreCodeToName->end(), code2Name ); + ID3GenreMap::value_type name2Code ( kAbbreviatedGenres[i].name, kAbbreviatedGenres[i].code ); + kMapID3GenreNameToCode->insert ( kMapID3GenreNameToCode->end(), name2Code ); + } + return true; + +} // InitializeGlobals + +// ================================================================================================= + +void TerminateGlobals() +{ + delete kMapID3GenreCodeToName; + delete kMapID3GenreNameToCode; + kMapID3GenreCodeToName = kMapID3GenreNameToCode = 0; +} + +// ================================================================================================= +// GenreUtils +// ================================================================================================= + +const char * GenreUtils::FindGenreName ( const std::string & code ) +{ + // Lookup a genre code and return its name if known, otherwise 0. + + const char * name = 0; + ID3GenreMap::iterator mapPos = kMapID3GenreCodeToName->find ( code.c_str() ); + if ( mapPos != kMapID3GenreCodeToName->end() ) name = mapPos->second; + return name; + +} + +// ================================================================================================= + +const char * GenreUtils::FindGenreCode ( const std::string & name ) +{ + // Lookup a genre name and return its code if known, otherwise 0. + + const char * code = 0; + ID3GenreMap::iterator mapPos = kMapID3GenreNameToCode->find ( name.c_str() ); + if ( mapPos != kMapID3GenreNameToCode->end() ) code = mapPos->second; + return code; + +} + +// ================================================================================================= + +static void StripOutsideSpaces ( std::string * value ) +{ + size_t length = value->size(); + size_t first, last; + + for ( first = 0; ((first < length) && ((*value)[first] == ' ')); ++first ) {} + if ( first == length ) { value->erase(); return; } + XMP_Assert ( (first < length) && ((*value)[first] != ' ') ); + + for ( last = length-1; ((last > first) && ((*value)[last] == ' ')); --last ) {} + if ( (first == 0) && (last == length-1) ) return; + + size_t newLen = last - first + 1; + if ( newLen < length ) *value = value->substr ( first, newLen ); + +} + +// ================================================================================================= + +void GenreUtils::ConvertGenreToXMP ( const char * id3Genre, std::string * xmpGenre ) +{ + // If the first character of TCON is not '(' then the entire TCON value is taken as the genre + // name and the suffix is empty. + // + // If the first character of TCON is '(' then the string up to ')' (or the end) is taken as the + // coded genre name. The rest of the TCON value after ')' is taken as the suffix. + // + // If the coded name is known then the corresponsing full name is used as the genre name, with + // no parens. + // + // If the coded name is not known then the coded name with parens is used as the genre name. + // + // The value of xmpDM:genre begins with the genre name. If the suffix is not empty we append + // "; " and the suffix. The known coded genre names currently do not use semicolon. + // + // Keeping the parens when importing unknown coded names might seem odd. But it preserves the + // ID3 syntax when exporting. Otherwise we would import "(XX)" and export "XX". We don't add + // parens all the time on export, that would import "Blues/R&B" and export "(Blues/R&B)". + + xmpGenre->erase(); + size_t id3Length = strlen ( id3Genre ); + if ( id3Length == 0 ) return; + + if ( id3Genre[0] != '(' ) { + // No left paren, take the whole TCON value as the XMP value. + xmpGenre->assign ( id3Genre, id3Length ); + StripOutsideSpaces ( xmpGenre ); + return; + } + + // The first character of TCON is '(', process the coded part and the suffix. + + size_t codeEnd; + std::string genreCode, suffix; + + for ( codeEnd = 1; ((codeEnd < id3Length) && (id3Genre[codeEnd] != ')')); ++codeEnd ) {} + genreCode.assign ( &id3Genre[1], codeEnd-1 ); + if ( codeEnd < id3Length ) suffix.assign ( &id3Genre[codeEnd+1], id3Length-codeEnd-1 ); + + StripOutsideSpaces ( &genreCode ); + StripOutsideSpaces ( &suffix ); + + if ( genreCode.empty() ) { + + (*xmpGenre) = suffix; // Degenerate case of "()suffix", treat as if "suffix". + + } else { + + const char * fullName = FindGenreName ( genreCode ); + + if ( fullName != 0 ) { + (*xmpGenre) = fullName; + } else { + (*xmpGenre) = '('; + (*xmpGenre) += genreCode; + (*xmpGenre) += ')'; + } + + if ( ! suffix.empty() ) { + (*xmpGenre) += "; "; + (*xmpGenre) += suffix; + } + + } + } // ================================================================================================= -void ID3_Support::TerminateGlobals() +void GenreUtils::ConvertGenreToID3 ( const char * xmpGenre, std::string * id3Genre ) { - // nothing yet + // The genre name is the xmpDM:genre value up to ';', with spaces at the front or back removed. + // The suffix is everything after ';', also with spaces at the front or back removed. + // + // If the genre name is known, it is replaced by the coded name in parens. + // + // The TCON value is the genre name plus the suffix. If the genre name does not end in ')' then + // a space is inserted. + + id3Genre->erase(); + size_t xmpLength = strlen ( xmpGenre ); + if ( xmpLength == 0 ) return; + + size_t nameEnd; + std::string genreName, suffix; + + for ( nameEnd = 0; ((nameEnd < xmpLength) && (xmpGenre[nameEnd] != ';')); ++nameEnd ) {} + genreName.assign ( xmpGenre, nameEnd ); + if ( nameEnd < xmpLength ) suffix.assign ( &xmpGenre[nameEnd+1], xmpLength-nameEnd-1 ); + + StripOutsideSpaces ( &genreName ); + StripOutsideSpaces ( &suffix ); + + if ( genreName.empty() ) { + + (*id3Genre) = suffix; // Degenerate case of "; suffix", treat as if "suffix". + + } else { + + const char * codedName = FindGenreCode ( genreName ); + if ( codedName != 0 ) { + genreName = '('; + genreName += codedName; + genreName += ')'; + } + + (*id3Genre) = genreName; + if ( ! suffix.empty() ) { + if ( genreName[genreName.size()-1] != ')' ) (*id3Genre) += ' '; + (*id3Genre) += suffix; + } + + } + } // ================================================================================================= // ID3Header // ================================================================================================= -bool ID3_Support::ID3Header::read ( XMP_IO* file ) +bool ID3Header::read ( XMP_IO* file ) { XMP_Assert ( sizeof(fields) == kID3_TagHeaderSize ); @@ -66,10 +404,10 @@ bool ID3_Support::ID3Header::read ( XMP_IO* file ) // ================================================================================================= -void ID3_Support::ID3Header::write ( XMP_IO* file, XMP_Int64 tagSize ) +void ID3Header::write ( XMP_IO* file, XMP_Int64 tagSize ) { - XMP_Assert ( (kID3_TagHeaderSize <= tagSize) && (tagSize < 256*1024*1024) ); // 256 MB limit due to synching. + XMP_Assert ( ((XMP_Int64)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] ); @@ -83,7 +421,7 @@ void ID3_Support::ID3Header::write ( XMP_IO* file, XMP_Int64 tagSize ) #define frameDefaults id(0), flags(0), content(0), contentSize(0), active(true), changed(false) -ID3_Support::ID3v2Frame::ID3v2Frame() : frameDefaults +ID3v2Frame::ID3v2Frame() : frameDefaults { XMP_Assert ( sizeof(fields) == kV23_FrameHeaderSize ); // Only need to do this in one place. memset ( this->fields, 0, kV23_FrameHeaderSize ); @@ -91,7 +429,7 @@ ID3_Support::ID3v2Frame::ID3v2Frame() : frameDefaults // ================================================================================================= -ID3_Support::ID3v2Frame::ID3v2Frame ( XMP_Uns32 id ) : frameDefaults +ID3v2Frame::ID3v2Frame ( XMP_Uns32 id ) : frameDefaults { memset ( this->fields, 0, kV23_FrameHeaderSize ); this->id = id; @@ -100,7 +438,7 @@ ID3_Support::ID3v2Frame::ID3v2Frame ( XMP_Uns32 id ) : frameDefaults // ================================================================================================= -void ID3_Support::ID3v2Frame::release() +void ID3v2Frame::release() { if ( this->content != 0 ) delete this->content; this->content = 0; @@ -109,7 +447,7 @@ void ID3_Support::ID3v2Frame::release() // ================================================================================================= -void ID3_Support::ID3v2Frame::setFrameValue ( const std::string& rawvalue, bool needDescriptor, +void ID3v2Frame::setFrameValue ( const std::string& rawvalue, bool needDescriptor, bool utf16, bool isXMPPRIVFrame, bool needEncodingByte ) { @@ -170,7 +508,7 @@ void ID3_Support::ID3v2Frame::setFrameValue ( const std::string& rawvalue, bool // ================================================================================================= -XMP_Int64 ID3_Support::ID3v2Frame::read ( XMP_IO* file, XMP_Uns8 majorVersion ) +XMP_Int64 ID3v2Frame::read ( XMP_IO* file, XMP_Uns8 majorVersion ) { XMP_Assert ( (2 <= majorVersion) && (majorVersion <= 4) ); @@ -213,7 +551,7 @@ XMP_Int64 ID3_Support::ID3v2Frame::read ( XMP_IO* file, XMP_Uns8 majorVersion ) // ================================================================================================= -void ID3_Support::ID3v2Frame::write ( XMP_IO* file, XMP_Uns8 majorVersion ) +void ID3v2Frame::write ( XMP_IO* file, XMP_Uns8 majorVersion ) { XMP_Assert ( (2 <= majorVersion) && (majorVersion <= 4) ); @@ -236,7 +574,7 @@ void ID3_Support::ID3v2Frame::write ( XMP_IO* file, XMP_Uns8 majorVersion ) // ================================================================================================= -bool ID3_Support::ID3v2Frame::advancePastCOMMDescriptor ( XMP_Int32& pos ) +bool ID3v2Frame::advancePastCOMMDescriptor ( XMP_Int32& pos ) { if ( (this->contentSize - pos) <= 3 ) return false; // silent error, no room left behing language tag @@ -267,7 +605,7 @@ bool ID3_Support::ID3v2Frame::advancePastCOMMDescriptor ( XMP_Int32& pos ) // ================================================================================================= -bool ID3_Support::ID3v2Frame::getFrameValue ( XMP_Uns8 majorVersion, XMP_Uns32 logicalID, std::string* utf8string ) +bool ID3v2Frame::getFrameValue ( XMP_Uns8 majorVersion, XMP_Uns32 logicalID, std::string* utf8string ) { XMP_Assert ( (this->content != 0) && (this->contentSize >= 0) && (this->contentSize < 20*1024*1024) ); @@ -349,9 +687,9 @@ bool ID3_Support::ID3v2Frame::getFrameValue ( XMP_Uns8 majorVersion, XMP_Uns32 l // ID3v1Tag // ================================================================================================= -bool ID3_Support::ID3v1Tag::read ( XMP_IO* file, SXMPMeta* meta ) +bool ID3v1Tag::read ( XMP_IO* file, SXMPMeta* meta ) { - // returns returns true, if ID3v1 (or v1.1) exists, otherwise false, sets XMP properties en route + // 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 ); @@ -410,8 +748,13 @@ bool ID3_Support::ID3v1Tag::read ( XMP_IO* file, SXMPMeta* meta ) } XMP_Uns8 genreNo = XIO::ReadUns8 ( file ); - if ( genreNo < 127 ) { - meta->SetProperty ( kXMP_NS_DM, "genre", Genres[genreNo] ); + if ( genreNo < numberedGenreCount ) { + meta->SetProperty ( kXMP_NS_DM, "genre", kNumberedGenres[genreNo].name ); + } else { + char buffer[4]; // AUDIT: Big enough for UInt8. + snprintf ( buffer, 4, "%d", genreNo ); + XMP_Assert ( strlen(buffer) == 3 ); // Should be in the range 126..255. + meta->SetProperty ( kXMP_NS_DM, "genre", buffer ); } return true; // ID3Tag found @@ -420,7 +763,25 @@ bool ID3_Support::ID3v1Tag::read ( XMP_IO* file, SXMPMeta* meta ) // ================================================================================================= -void ID3_Support::ID3v1Tag::write ( XMP_IO* file, SXMPMeta* meta ) +static inline bool GetDecimalUns32 ( const char * str, XMP_Uns32 * bin ) +{ + XMP_Assert ( bin != 0 ); + if ( (str == 0) || (str[0] == 0) ) return false; + + *bin = 0; + for ( size_t i = 0; str[i] != 0; ++i ) { + char ch = str[i]; + if ( (ch < '0') || (ch > '9') ) return false; + *bin = (*bin * 10) + (ch - '0'); + } + + return true; + +} + +// ================================================================================================= + +void ID3v1Tag::write ( XMP_IO* file, SXMPMeta* meta ) { std::string zeros ( 128, '\0' ); @@ -470,21 +831,23 @@ void ID3_Support::ID3v1Tag::write ( XMP_IO* file, SXMPMeta* meta ) 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; + // Write the first genre code as a UInt8. + size_t nameEnd; + std::string name; + + for ( nameEnd = 0; ((nameEnd < utf8.size()) && (utf8[nameEnd] != ';')); ++nameEnd ) {} + name.assign ( utf8.c_str(), nameEnd ); + const char * code = GenreUtils::FindGenreCode ( name ); + + if ( code != 0 ) { + XMP_Uns32 value; + bool ok = GetDecimalUns32 ( code, &value ); + if ( ok && (value <= 255) ) { + file->Seek ( (-128 + 127), kXMP_SeekFromEnd ); + XIO::WriteUns8 ( file, (XMP_Uns8)value ); } } - file->Seek ( (-128 + 127), kXMP_SeekFromEnd ); - XIO::WriteUns8 ( file, genreNo ); - } if ( meta->GetProperty ( kXMP_NS_DM, "trackNumber", &utf8, kXMP_NoOptions ) ) { @@ -502,3 +865,7 @@ void ID3_Support::ID3v1Tag::write ( XMP_IO* file, SXMPMeta* meta ) } } // ID3v1Tag::write + +// ================================================================================================= + +}; // namespace ID3_Support diff --git a/XMPFiles/source/FormatSupport/ID3_Support.hpp b/XMPFiles/source/FormatSupport/ID3_Support.hpp index 43b917d..5228fd7 100644 --- a/XMPFiles/source/FormatSupport/ID3_Support.hpp +++ b/XMPFiles/source/FormatSupport/ID3_Support.hpp @@ -42,137 +42,6 @@ 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 ) { @@ -191,8 +60,21 @@ namespace ID3_Support { // ============================================================================================= - bool InitializeGlobals(); + bool InitializeGlobals(); // Initialize and terminate the known genre maps. void TerminateGlobals(); + + // ============================================================================================= + + namespace GenreUtils { + + void ConvertGenreToXMP ( const char * id3Genre, std::string * xmpGenre ); + void ConvertGenreToID3 ( const char * xmpGenre, std::string * id3Genre ); + + // Internal utilities, exposed for unit testing: + const char * FindGenreName ( const std::string & code ); + const char * FindGenreCode ( const std::string & name ); + + }; // ============================================================================================= diff --git a/XMPFiles/source/FormatSupport/IFF/Chunk.cpp b/XMPFiles/source/FormatSupport/IFF/Chunk.cpp index a2de741..9e1407f 100644 --- a/XMPFiles/source/FormatSupport/IFF/Chunk.cpp +++ b/XMPFiles/source/FormatSupport/IFF/Chunk.cpp @@ -538,6 +538,52 @@ XMP_Uns64 Chunk::calculateSize( bool setOriginal /*= false*/ ) //----------------------------------------------------------------------------- // +// Chunk::calculateWriteSize(...) +// +// Purpose: Calculate the size of the chunks that are dirty including the size +// of its children +// +//----------------------------------------------------------------------------- + +XMP_Int64 Chunk::calculateWriteSize( ) const +{ + XMP_Int64 size=0; + if (hasChanged()) + { + size+=(sizeof(XMP_Uns32)*2); + if (mChunkMode == CHUNK_LEAF) + { + if ( mSize % 2 == 1 ) + { + // for odd file sizes, a pad byte is written + size+=(mSize+1); + } + else + { + size+=mSize; + } + } + else // mChunkMode == CHUNK_NODE + { + // writes type if defined + if (mChunkId.type != kType_NONE) + { + size+=sizeof(XMP_Uns32); + } + + // calls calculateWriteSize recursively on it's children + for( ConstChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ ) + { + size+=(*iter)->calculateWriteSize( ); + } + } + } + + return size; +} + +//----------------------------------------------------------------------------- +// // Chunk::setOffset(...) // // Purpose: Adjust the offset that this chunk has within the file diff --git a/XMPFiles/source/FormatSupport/IFF/Chunk.h b/XMPFiles/source/FormatSupport/IFF/Chunk.h index 2d170ed..ef2ba47 100644 --- a/XMPFiles/source/FormatSupport/IFF/Chunk.h +++ b/XMPFiles/source/FormatSupport/IFF/Chunk.h @@ -195,6 +195,12 @@ class Chunk : public IChunkData, */ inline void setSize( XMP_Uns64 newSize, bool setOriginal = false ) { mDirty = mSize != newSize; mSize = newSize; mOriginalSize = setOriginal ? newSize : mOriginalSize; } + /** + * Calculate the size of the chunks that are dirty including the size + * of its children + */ + XMP_Int64 calculateWriteSize( ) const; + /** * Calculate the size of this chunks based on its children sizes. * If this chunk has no children then no new size will be calculated. diff --git a/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp b/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp index 786cbd1..2a5a322 100644 --- a/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp +++ b/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp @@ -321,7 +321,7 @@ void ChunkController::parseFile( XMP_IO* stream, XMP_OptionBits* options /* = NU // Purpose: Called by the handler to write back the changes to the file. // //----------------------------------------------------------------------------- -void ChunkController::writeFile( XMP_IO* stream ) +void ChunkController::writeFile( XMP_IO* stream ,XMP_ProgressTracker * progressTracker ) { // @@ -344,11 +344,27 @@ void ChunkController::writeFile( XMP_IO* stream ) // 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); + if ( progressTracker != 0 ) + { + float fileWriteSize=0.0f; + for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ ) + { + Chunk* child = mRoot->getChildAt(i); + fileWriteSize+=child->calculateWriteSize( ); + } + XMP_Assert ( progressTracker->WorkInProgress() ); + progressTracker->AddTotalWork ( fileWriteSize ); + } // Move garbage tail after last top-level chunk, // BEFORE the chunks are written -- in case the file shrinks if (mTrailingGarbageSize > 0 && newFileSize != mTrailingGarbageOffset) { + if ( progressTracker != 0 ) + { + XMP_Assert ( progressTracker->WorkInProgress() ); + progressTracker->AddTotalWork ( (float)mTrailingGarbageSize ); + } XIO::Move( stream, mTrailingGarbageOffset, stream, newFileSize, mTrailingGarbageSize ); newFileSize += mTrailingGarbageSize; } diff --git a/XMPFiles/source/FormatSupport/IFF/ChunkController.h b/XMPFiles/source/FormatSupport/IFF/ChunkController.h index 933da36..52dea42 100644 --- a/XMPFiles/source/FormatSupport/IFF/ChunkController.h +++ b/XMPFiles/source/FormatSupport/IFF/ChunkController.h @@ -16,6 +16,7 @@ #include "public/include/XMP_IO.hpp" #include "source/XMP_LibUtils.hpp" +#include "source/XMP_ProgressTracker.hpp" #include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h" #include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h" @@ -105,8 +106,9 @@ class ChunkController * 2. write the changed chunks to the file * * @param stream the open [file] stream for writing, the file pointer must be at the beginning + * @param progressTracker Progress tracker to track the file write progress and reporting it to client */ - void writeFile( XMP_IO* stream ); + void writeFile( XMP_IO* stream,XMP_ProgressTracker * progressTracker ); /** * Returns the first (or last) Chunk that matches the passed path. diff --git a/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp b/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp index 115a0e8..dd2fbea 100644 --- a/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp +++ b/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp @@ -33,6 +33,7 @@ namespace ISOMedia { k_mp41 = 0x6D703431UL, // Compatible brand codes k_mp42 = 0x6D703432UL, k_f4v = 0x66347620UL, + k_avc1 = 0x61766331UL, k_qt = 0x71742020UL, k_moov = 0x6D6F6F76UL, // Container Box, no version/flags. @@ -58,6 +59,9 @@ namespace ISOMedia { k_stsc = 0x73747363UL, k_stco = 0x7374636FUL, k_co64 = 0x636F3634UL, + k_dinf = 0x64696E66UL, + k_dref = 0x64726566UL, + k_alis = 0x616C6973UL, k_meta = 0x6D657461UL, // Types for the iTunes metadata boxes. k_ilst = 0x696C7374UL, diff --git a/XMPFiles/source/FormatSupport/MOOV_Support.cpp b/XMPFiles/source/FormatSupport/MOOV_Support.cpp index b9b4996..959df44 100644 --- a/XMPFiles/source/FormatSupport/MOOV_Support.cpp +++ b/XMPFiles/source/FormatSupport/MOOV_Support.cpp @@ -272,6 +272,7 @@ void MOOV_Manager::ParseNestedBoxes ( BoxNode * parentNode, const std::string & case ISOMedia::k_edts : pathSuffix = "/edts"; break; case ISOMedia::k_mdia : pathSuffix = "/mdia"; break; case ISOMedia::k_minf : pathSuffix = "/minf"; break; + case ISOMedia::k_dinf : pathSuffix = "/dinf"; break; case ISOMedia::k_stbl : pathSuffix = "/stbl"; break; } if ( pathSuffix != 0 ) { diff --git a/XMPFiles/source/FormatSupport/MOOV_Support.hpp b/XMPFiles/source/FormatSupport/MOOV_Support.hpp index 814a8bb..1dace2a 100644 --- a/XMPFiles/source/FormatSupport/MOOV_Support.hpp +++ b/XMPFiles/source/FormatSupport/MOOV_Support.hpp @@ -104,7 +104,7 @@ public: // --------------------------------------------------------------------------------------------- - #pragma pack (1) // ! These must match the file layout! + #pragma pack (push, 1) // ! These must match the file layout! struct Content_mvhd_0 { XMP_Uns32 vFlags; // 0 @@ -164,6 +164,12 @@ public: XMP_Uns32 sampleDescrID; // 8 }; // 12 + #pragma pack( pop ) + +#if SUNOS_SPARC + #pragma pack( ) +#endif //#if SUNOS_SPARC + // --------------------------------------------------------------------------------------------- MOOV_Manager() : fileMode(0) diff --git a/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp b/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp index aecfec1..5d06220 100644 --- a/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp +++ b/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp @@ -273,12 +273,6 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, 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 @@ -286,45 +280,54 @@ void PSIR_FileWriter::ParseFileResources ( XMP_IO* fileRef, XMP_Uns32 length ) // taken though if the current one is empty. // ! Don't use map[id] to lookup, that creates a default entry if none exists! + + // PSIR layout: + // - Uns32 type, usually '8BIM' + // - Uns16 ID + // - PString name + // - Uns8 optional pad for even alignment + // - Uns32 data size + // - data + // - Uns8 optional pad for even alignment + + static const size_t kMinPSIRSize = 12; // 4+2+1+1+4 + + this->DeleteExistingInfo(); + this->fileParsed = true; + if ( length == 0 ) return; - 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; + XMP_Int64 psirOrigin = fileRef->Offset(); // Need this to determine the resource data offsets. + XMP_Int64 fileEnd = psirOrigin + length; - std::string rsrcPName; + char nameBuffer [260]; // The name is a PString, at 1+255+1 including length and pad. - while ( (ioBuf.filePos + (ioBuf.ptr - ioBuf.data)) < fileEnd ) { + while ( fileRef->Offset() < fileEnd ) { - ok = CheckFileSpace ( fileRef, &ioBuf, 12 ); // The minimal image resource takes 12 bytes. - if ( ! ok ) break; // Bad image resource. Throw instead? + if ( ! XIO::CheckFileSpace ( fileRef, kMinPSIRSize ) ) break; // Bad image resource. - XMP_Int64 thisRsrcPos = ioBuf.filePos + (ioBuf.ptr - ioBuf.data); + XMP_Int64 thisRsrcPos = fileRef->Offset(); - XMP_Uns32 type = GetUns32BE(ioBuf.ptr); - XMP_Uns16 id = GetUns16BE(ioBuf.ptr+4); - ioBuf.ptr += 6; // Advance to the resource name. + XMP_Uns32 type = XIO::ReadUns32_BE ( fileRef ); + XMP_Uns16 id = XIO::ReadUns16_BE ( fileRef ); - XMP_Uns16 nameLen = ioBuf.ptr[0]; // ! The length for the Pascal string. + XMP_Uns8 nameLen = XIO::ReadUns8 ( fileRef ); // ! 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 ( ! XIO::CheckFileSpace ( fileRef, paddedLen+4 ) ) break; // Bad image resource. - if ( nameLen > 0 ) rsrcPName.assign ( (char*)(ioBuf.ptr), paddedLen ); // ! Include the length byte and pad. + nameBuffer[0] = nameLen; + fileRef->ReadAll ( &nameBuffer[1], paddedLen-1 ); // Include the pad byte, present for zero nameLen. - ioBuf.ptr += paddedLen; // Move to the data length. - XMP_Uns32 dataLen = GetUns32BE(ioBuf.ptr); + XMP_Uns32 dataLen = XIO::ReadUns32_BE ( fileRef ); XMP_Uns32 dataTotal = ((dataLen + 1) & 0xFFFFFFFEUL); // Round up to an even total. - ioBuf.ptr += 4; // Advance to the resource data. + if ( ! XIO::CheckFileSpace ( fileRef, dataTotal ) ) break; // Bad image resource. - XMP_Int64 thisDataPos = ioBuf.filePos + (ioBuf.ptr - ioBuf.data); + XMP_Int64 thisDataPos = fileRef->Offset(); 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 ); + fileRef->Seek ( nextRsrcPos, kXMP_SeekFromStart ); continue; } @@ -335,7 +338,7 @@ void PSIR_FileWriter::ParseFileResources ( XMP_IO* fileRef, XMP_Uns32 length ) } else if ( (rsrcPos->second.dataLen == 0) && (newInfo.dataLen != 0) ) { rsrcPos->second = newInfo; } else { - MoveToOffset ( fileRef, nextRsrcPos, &ioBuf ); + fileRef->Seek ( nextRsrcPos, kXMP_SeekFromStart ); continue; } InternalRsrcInfo* rsrcPtr = &rsrcPos->second; @@ -345,29 +348,17 @@ void PSIR_FileWriter::ParseFileResources ( XMP_IO* fileRef, XMP_Uns32 length ) 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. + memcpy ( (void*)rsrcPtr->rsrcName, nameBuffer, paddedLen ); // AUDIT: Safe, allocated enough bytes above. } if ( ! IsMetadataImgRsrc ( id ) ) { - MoveToOffset ( fileRef, nextRsrcPos, &ioBuf ); + fileRef->Seek ( nextRsrcPos, kXMP_SeekFromStart ); continue; } - rsrcPtr->dataPtr = malloc ( dataLen ); // ! Allocate after the IsMetadataImgRsrc check. + rsrcPtr->dataPtr = malloc ( dataTotal ); // ! 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 ); - } + fileRef->ReadAll ( (void*)rsrcPtr->dataPtr, dataTotal ); } @@ -492,11 +483,10 @@ XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr ) // ==================================== XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* destRef, - void * _ioBuf, XMP_AbortProc abortProc, void * abortArg ) + XMP_AbortProc abortProc, void * abortArg, + XMP_ProgressTracker* progressTracker ) { - IgnoreParam(_ioBuf); const XMP_Uns32 zero32 = 0; - const bool checkAbort = (abortProc != 0); struct RsrcHeader { @@ -507,9 +497,29 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* dest if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure ); - XMP_Int64 destLenOffset = destRef->Offset(); - XMP_Uns32 destLength = 0; + InternalRsrcMap::const_iterator rsrcPos; + InternalRsrcMap::const_iterator rsrcEnd = this->imgRsrcs.end(); + + if ( progressTracker != 0 ) { + + float totalLength = 8; + for ( rsrcPos = this->imgRsrcs.begin(); rsrcPos != rsrcEnd; ++rsrcPos ) { + const InternalRsrcInfo& currRsrc = rsrcPos->second; + totalLength += (currRsrc.dataLen + 12); + } + + size_t sizeOtherRsrc = this->otherRsrcs.size(); + for ( size_t i = 0; i < sizeOtherRsrc; ++i ) { + totalLength += this->otherRsrcs[i].rsrcLength; + } + + XMP_Assert ( progressTracker->WorkInProgress() ); + progressTracker->AddTotalWork ( totalLength ); + + } + XMP_Uns32 destLength = 0; + XMP_Int64 destLenOffset = destRef->Offset(); destRef->Write ( &destLength, 4 ); // Write a placeholder for the new PSIR section length. #if 0 @@ -532,13 +542,10 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* dest 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 ) { + for ( rsrcPos = this->imgRsrcs.begin(); rsrcPos != rsrcEnd; ++rsrcPos ) { - InternalRsrcInfo& currRsrc = rsrcPos->second; + const InternalRsrcInfo& currRsrc = rsrcPos->second; outHeader.id = MakeUns16BE ( currRsrc.id ); destRef->Write ( &outHeader, 6 ); diff --git a/XMPFiles/source/FormatSupport/PSIR_Support.hpp b/XMPFiles/source/FormatSupport/PSIR_Support.hpp index b0ec13a..bd231b3 100644 --- a/XMPFiles/source/FormatSupport/PSIR_Support.hpp +++ b/XMPFiles/source/FormatSupport/PSIR_Support.hpp @@ -19,6 +19,7 @@ #include "source/XMPFiles_IO.hpp" #include "source/EndianUtils.hpp" +#include "source/XMP_ProgressTracker.hpp" #include <map> @@ -140,8 +141,8 @@ public: 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. + XMP_AbortProc abortProc, void * abortArg, + XMP_ProgressTracker* progressTracker ) = 0; // --------------------------------------------------------------------------------------------- @@ -179,8 +180,8 @@ public: 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. + XMP_AbortProc abortProc, void * abortArg, + XMP_ProgressTracker* progressTracker ) { NotAppropriate(); return 0; }; PSIR_MemoryReader() : ownedContent(false), psirLength(0), psirContent(0) {}; @@ -232,8 +233,8 @@ public: 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. + XMP_AbortProc abortProc, void * abortArg, + XMP_ProgressTracker* progressTracker ); PSIR_FileWriter() : changed(false), legacyDeleted(false), memParsed(false), fileParsed(false), ownedContent(false), memLength(0), memContent(0) {}; diff --git a/XMPFiles/source/FormatSupport/PackageFormat_Support.cpp b/XMPFiles/source/FormatSupport/PackageFormat_Support.cpp new file mode 100644 index 0000000..9405293 --- /dev/null +++ b/XMPFiles/source/FormatSupport/PackageFormat_Support.cpp @@ -0,0 +1,124 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2013 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 "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" +#include <algorithm> + +// ================================================================================================= +/// \file PackageFormat_Support.cpp +/// +// ================================================================================================= + + +// ================================================================================================= +// PackageFormat_Support::GetPostfixRange +// ================================ + +#if 0 +XMP_Bool PackageFormat_Support::GetPostfixRange ( XMP_FileFormat format , XMP_StringPtr extension, XMP_Uns32 * range ) +{ + switch ( format ) { + case kXMP_XDCAM_EXFile: + range[0] = 1; range[1] = 99; + break; + case kXMP_CanonXFFile: + range[0] = 1; range[1] = 99; + break; + case kXMP_XDCAM_SAMFile: + case kXMP_XDCAM_FAMFile: + range[0] = 1; range[1] = 99; + break; + case kXMP_P2File: + range[0] = 0; range[1] = 99;// for voice memo files + if(strcmp(extension, ".MXF") == 0) + range[1] = 15;// for audio essence files + break; + default: + return false; + } + return true; +} // PackageFormat_Support::GetPostfixRange +#endif + +// ================================================================================================= +// PackageFormat_Support::AddResourceIfExists +// ================================ + +bool PackageFormat_Support::AddResourceIfExists ( XMP_StringVector * resourceList, const XMP_VarString & file ) +{ + if ( Host_IO::Exists ( file.c_str() ) ) { + resourceList->push_back ( file ); + return true; + } + return false; +} // PackageFormat_Support::AddResourceIfExists + +#if 0 +// ================================================================================================= +// PackageFormat_Support::AddResourceIfExists +// ================================ + +bool PackageFormat_Support::AddResourceIfExists ( XMP_StringVector * resourceList, const XMP_VarString & noExtPath, + XMP_StringPtr extension, XMP_FileFormat format ) +{ + XMP_Uns32 range[2]; + XMP_Bool atLeastOneFileAdded = false, fileAdded = false; + XMP_VarString iStr, filePath; + if ( GetPostfixRange ( format, extension, range ) ) { + for( XMP_Uns32 index = range[0]; index <= range[1] ; ++index ) + { + SXMPUtils::ConvertFromInt ( index, NULL, &iStr ) ; + if ( index < LEAST_TWO_DIGIT_INT ) + iStr = '0' + iStr; + filePath = noExtPath + iStr + extension; + fileAdded = AddResourceIfExists ( resourceList, filePath ); + atLeastOneFileAdded |= fileAdded ; + } + } + return atLeastOneFileAdded; +} // PackageFormat_Support::AddResourceIfExists + +#endif +// ================================================================================================= +// PackageFormat_Support::AddResourceIfExists +// ================================ +bool PackageFormat_Support::AddResourceIfExists ( XMP_StringVector * resourceList, const XMP_VarString & folderPath, + XMP_StringPtr prefix, XMP_StringPtr postfix ) +{ + Host_IO::FolderRef folderHandle = Host_IO::OpenFolder ( folderPath.c_str() ); + if ( folderHandle == Host_IO::noFolderRef || !prefix || !postfix ) + return false;// can't open folder. + XMP_VarString fileName, filePath; + size_t fileNameLength; + size_t prefixLength = strlen ( prefix ); + size_t postfixLength = strlen ( postfix ); + bool atleastOneFileAdded = false; + while ( Host_IO::GetNextChild ( folderHandle, &fileName ) ) + { + fileNameLength = fileName.length(); + // Check if the file name starts with prefix and ends with postfix + if ( fileNameLength >= ( prefixLength + postfixLength ) && + fileName.compare ( fileNameLength-postfixLength, postfixLength, postfix ) == 0 && + fileName.compare ( 0, prefixLength, prefix ) == 0) + { + filePath = folderPath + kDirChar + fileName; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + atleastOneFileAdded = true; + } + } + // close folder + Host_IO::CloseFolder ( folderHandle ); + return atleastOneFileAdded; +} // PackageFormat_Support::AddResourceIfExists + + +// ================================================================================================= diff --git a/XMPFiles/source/FormatSupport/PackageFormat_Support.hpp b/XMPFiles/source/FormatSupport/PackageFormat_Support.hpp new file mode 100644 index 0000000..8883c9e --- /dev/null +++ b/XMPFiles/source/FormatSupport/PackageFormat_Support.hpp @@ -0,0 +1,39 @@ +#ifndef __PackageFormat_Support_hpp__ +#define __PackageFormat_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2013 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 "source/XMP_LibUtils.hpp" + +// ================================================================================================= +/// \file PackageFormat_Support.hpp +/// \brief XMPFiles support for folder based formats. +/// +// ================================================================================================= + +namespace PackageFormat_Support +{ + + // Checks if the file at path "file" exists. + // If it exists then it adds to "resourceList" and returns true. + bool AddResourceIfExists ( XMP_StringVector * resourceList, const XMP_VarString & file ); + + // This function adds all the existing files in the specified folder whose name starts with prefix and ends with postfix. + bool AddResourceIfExists ( XMP_StringVector * resourceList, const XMP_VarString & folderPath, + XMP_StringPtr prefix, XMP_StringPtr postfix); + + +} // namespace PackageFormat_Support + +// ================================================================================================= + +#endif // __PackageFormat_Support_hpp__ diff --git a/XMPFiles/source/FormatSupport/PostScript_Support.cpp b/XMPFiles/source/FormatSupport/PostScript_Support.cpp new file mode 100644 index 0000000..a0c13d2 --- /dev/null +++ b/XMPFiles/source/FormatSupport/PostScript_Support.cpp @@ -0,0 +1,1094 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2012 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/PostScript_Support.hpp" +#include "XMP.hpp" +#include <algorithm> +#include <climits> + +// ================================================================================================= +// PostScript_Support::HasCodesGT127 +// ================================= +// +// function to detect character codes greater than 127 in a string +bool PostScript_Support::HasCodesGT127(const std::string & value) +{ + size_t vallen=value.length(); + for (size_t index=0;index<vallen;index++) + { + if ((unsigned char)value[index]>127) + { + return true; + } + } + return false; +} + +// ================================================================================================= +// PostScript_Support::SkipTabsAndSpaces +// ===================================== +// +// function moves the file pointer ahead such that it skips all tabs and spaces +bool PostScript_Support::SkipTabsAndSpaces(XMP_IO* file,IOBuffer& ioBuf) +{ + while ( true ) + { + if ( ! CheckFileSpace ( file, &ioBuf, 1 ) ) return false; + if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) break; + ++ioBuf.ptr; + } + return true; +} + +// ================================================================================================= +// PostScript_Support::SkipUntilNewline +// ==================================== +// +// function moves the file pointer ahead such that it skips all characters until a newline +bool PostScript_Support::SkipUntilNewline(XMP_IO* file,IOBuffer& ioBuf) +{ + char ch; + do + { + if ( ! CheckFileSpace ( file, &ioBuf, 1 ) ) return false; + ch = *ioBuf.ptr; + ++ioBuf.ptr; + } while ( ! IsNewline ( ch ) ); + if (ch==kCR &&*ioBuf.ptr==kLF) + { + if ( ! CheckFileSpace ( file, &ioBuf, 1 ) ) return false; + ++ioBuf.ptr; + } + return true; +} + + +// ================================================================================================= +// RevRefillBuffer and RevCheckFileSpace +// ====================================== +// +// These helpers are similar to RefillBuffer and CheckFileSpace with the difference that the it traverses +// the file stream in reverse order +void PostScript_Support::RevRefillBuffer ( XMP_IO* fileRef, IOBuffer* ioBuf ) +{ + // Refill including part of the current data, seek back to the new buffer origin and read. + size_t reverseSeek = ioBuf->limit - ioBuf->ptr; + if (ioBuf->filePos>kIOBufferSize) + { + ioBuf->filePos = fileRef->Seek ( -((XMP_Int64)(kIOBufferSize+reverseSeek)), kXMP_SeekFromCurrent ); + ioBuf->len = fileRef->Read ( &ioBuf->data[0], kIOBufferSize ); + ioBuf->ptr = &ioBuf->data[0]+ioBuf->len; + ioBuf->limit = ioBuf->ptr ; + } + else + { + XMP_Int64 rev = (ioBuf->ptr-&ioBuf->data[0]) + ioBuf->filePos; + ioBuf->filePos = fileRef->Seek ( 0, kXMP_SeekFromStart ); + ioBuf->len = fileRef->Read ( &ioBuf->data[0], kIOBufferSize ); + if ( rev > (XMP_Int64)ioBuf->len )throw XMP_Error ( kXMPErr_ExternalFailure, "Seek failure in FillBuffer" ); + ioBuf->ptr = &ioBuf->data[0]+rev; + ioBuf->limit = &ioBuf->data[0]+ioBuf->len; + } + + +} +bool PostScript_Support::RevCheckFileSpace ( XMP_IO* fileRef, IOBuffer* ioBuf, size_t neededLen ) +{ + if ( size_t(ioBuf->ptr - &ioBuf->data[0]) < size_t(neededLen) ) + { // ! Avoid VS.Net compare warnings. + PostScript_Support::RevRefillBuffer ( fileRef, ioBuf ); + } + return (size_t(ioBuf->ptr - &ioBuf->data[0]) >= size_t(neededLen)); +} + +// ================================================================================================= +// SearchBBoxInTrailer +// =================== +// +// Function searches the Bounding Box in the comments after the %Trailer +// this function gets called when the DSC comment BoundingBox: value is +// (atend) +// returns true if atleast one BoundingBox: is found after %Trailer +inline static bool SearchBBoxInTrailer(XMP_IO* fileRef,IOBuffer& ioBuf) +{ + bool bboxfoundintrailer=false; + if ( ! PostScript_Support::SkipTabsAndSpaces( fileRef, ioBuf ) ) return false; + if ( ! IsNewline ( *ioBuf.ptr ) ) return false; + ++ioBuf.ptr; + // Scan for all the %%Trailer outside %%BeginDocument: & %%EndDocument comments + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsBeginDocString.length() ) ) return false; + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsTrailerString.c_str()), kPSContainsTrailerString.length() )) + { + //found %%Trailer now search for proper %%BoundingBox + ioBuf.ptr+=kPSContainsTrailerString.length(); + //skip chars after %%Trailer till newline + if ( ! PostScript_Support::SkipUntilNewline( fileRef, ioBuf ) ) return false; + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsBBoxString.length() ) ) return false; + //check for "%%BoundingBox:" + if (CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsBBoxString.c_str()), kPSContainsBBoxString.length() ) ) + { + //found "%%BoundingBox:" + ioBuf.ptr+=kPSContainsBBoxString.length(); + // Skip leading spaces and tabs. + if ( ! PostScript_Support::SkipTabsAndSpaces( fileRef, ioBuf ) ) return false; + if ( IsNewline ( *ioBuf.ptr ) ) return false; // Reached the end of the %%BoundingBox comment. + bboxfoundintrailer=true; + break; + } + //skip chars till newline + if ( ! PostScript_Support::SkipUntilNewline( fileRef, ioBuf ) ) return false; + } + if (!bboxfoundintrailer) + return false; + else + break; + } + else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsBeginDocString.c_str()), kPSContainsBeginDocString.length() ) ) + { + //"%%BeginDocument:" Found search for "%%EndDocument" + ioBuf.ptr+=kPSContainsBeginDocString.length(); + //skip chars after "%%BeginDocument:" till newline + if ( ! PostScript_Support::SkipUntilNewline( fileRef, ioBuf ) ) return false; + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsEndDocString.length() ) ) return false; + //check for "%%EndDocument" + if (CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsEndDocString.c_str()), kPSContainsEndDocString.length() ) ) + { + //found "%%EndDocument" + ioBuf.ptr+=kPSContainsEndDocString.length(); + break; + } + //skip chars till newline + if ( ! PostScript_Support::SkipUntilNewline( fileRef, ioBuf ) ) return false; + }// while to search %%EndDocument + + } //else if to consume a pair of %%BeginDocument: and %%EndDocument + //skip chars till newline + if ( ! PostScript_Support::SkipUntilNewline( fileRef, ioBuf ) ) return false; + }// while for scanning %%BoundingBox after %%Trailer + if (!bboxfoundintrailer) return false; + return true; +} + +// ================================================================================================= +// PostScript_Support::IsValidPSFile +// ================================= +// +// Determines if the file is a valid PostScript or EPS file +// Checks done +// Looks for a valid Poscript header +// For EPS file checks for a valid Bounding Box comment +bool PostScript_Support::IsValidPSFile(XMP_IO* fileRef,XMP_FileFormat &format) +{ + IOBuffer ioBuf; + XMP_Int64 psOffset; + size_t psLength; + XMP_Uns32 fileheader,psMajorVer, psMinorVer,epsMajorVer,epsMinorVer; + char ch; + // Check for the binary EPSF preview header. + + fileRef->Rewind(); + if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; + fileheader = GetUns32BE ( ioBuf.ptr ); + + if ( fileheader == 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, (kPSFileTag.length() + 3 + 1) ) ) return false; + if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSFileTag.c_str()), kPSFileTag.length() ) ) return false; + ioBuf.ptr += kPSFileTag.length(); + + // Check the PostScript DSC major version number. + + psMajorVer = 0; + while ( (ioBuf.ptr < ioBuf.limit) && IsNumeric( *ioBuf.ptr ) ) + { + psMajorVer = (psMajorVer * 10) + (*ioBuf.ptr - '0'); + if ( psMajorVer > 1000 ) return false; // Overflow. + ioBuf.ptr += 1; + } + if ( psMajorVer < 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. + + psMinorVer = 0; + while ( (ioBuf.ptr < ioBuf.limit) && IsNumeric( *ioBuf.ptr ) ) + { + psMinorVer = (psMinorVer * 10) + (*ioBuf.ptr - '0'); + if ( psMinorVer > 1000 ) return false; // Overflow. + ioBuf.ptr += 1; + } + + switch( format ) + { + case kXMP_PostScriptFile: + { + // Almost done for plain PostScript, check for whitespace. + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if ( ! IsWhitespace(*ioBuf.ptr) ) return false; + ioBuf.ptr += 1; + + break; + } + case kXMP_UnknownFile: + { + if ( ! SkipTabsAndSpaces ( fileRef, ioBuf ) ) return false; + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 5 ) ) return false; + // checked PS header to this point Atkleast a PostScript File + format=kXMP_PostScriptFile; + //return true if no "EPSF-" is found as it is a valid PS atleast + if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr("EPSF-"), 5 ) ) return true; + } + case kXMP_EPSFile: + { + + if ( ! SkipTabsAndSpaces ( fileRef, ioBuf ) ) return false; + // Check for the EPSF keyword on the header comment. + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 5+3+1 ) ) return false; + if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr("EPSF-"), 5 ) ) return false; + ioBuf.ptr += 5; + + // Check the EPS major version number. + + epsMajorVer = 0; + while ( (ioBuf.ptr < ioBuf.limit) &&IsNumeric( *ioBuf.ptr ) ) { + epsMajorVer = (epsMajorVer * 10) + (*ioBuf.ptr - '0'); + if ( epsMajorVer > 1000 ) return false; // Overflow. + ioBuf.ptr += 1; + } + if ( epsMajorVer < 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. + + epsMinorVer = 0; + while ( (ioBuf.ptr < ioBuf.limit) && IsNumeric( *ioBuf.ptr ) ) { + epsMinorVer = (epsMinorVer * 10) + (*ioBuf.ptr - '0'); + if ( epsMinorVer > 1000 ) return false; // Overflow. + ioBuf.ptr += 1; + } + + if ( ! SkipTabsAndSpaces ( fileRef, ioBuf ) ) return false; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if ( ! IsNewline( *ioBuf.ptr ) ) return false; + ch=*ioBuf.ptr; + ioBuf.ptr += 1; + if (ch==kCR &&*ioBuf.ptr==kLF) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + ++ioBuf.ptr; + } + + + while ( true ) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsBBoxString.length() ) ) return false; + + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() ) //explicit endcommentcheck + || *ioBuf.ptr!='%' || !(*(ioBuf.ptr+1)>32 && *(ioBuf.ptr+1)<=126 )) // implicit endcomment check + { + // Found "%%EndComments", don't look any further. + return false; + } + else if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsBBoxString.c_str()), kPSContainsBBoxString.length() ) ) + { + // Not "%%EndComments" or "%%BoundingBox:", skip past the end of this line. + if ( ! SkipUntilNewline ( fileRef, ioBuf ) ) return true; + } + else + { + + // Found "%%BoundingBox:", look for llx lly urx ury. + ioBuf.ptr += kPSContainsBBoxString.length(); + //Check for atleast a mandatory space b/w "%%BoundingBox:" and llx lly urx ury + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) return false; + ioBuf.ptr++; + + while ( true ) + { + // Skip leading spaces and tabs. + if ( ! SkipTabsAndSpaces( fileRef, ioBuf ) ) return false; + if ( IsNewline ( *ioBuf.ptr ) ) return false; // Reached the end of the %%BoundingBox comment. + + //if the comment is %%BoundingBox: (atend) go past the %%Trailer to check BBox + bool bboxfoundintrailer=false; + if (*ioBuf.ptr=='(') + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsAtendString.length() ) ) return false; + + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsAtendString.c_str()), kPSContainsAtendString.length() ) ) + { + // search for Bounding Box Past Trailer + ioBuf.ptr += kPSContainsAtendString.length(); + bboxfoundintrailer=SearchBBoxInTrailer( fileRef, ioBuf ); + } + + if (!bboxfoundintrailer) + return false; + + }//if (*ioBuf.ptr=='(') + + int noOfIntegers=0; + // verifies for llx lly urx ury. + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if(IsPlusMinusSign(*ioBuf.ptr )) + ++ioBuf.ptr; + bool atleastOneNumeric=false; + while ( true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (!IsNumeric ( *ioBuf.ptr ) ) break; + ++ioBuf.ptr; + atleastOneNumeric=true; + } + if (!atleastOneNumeric) return false; + + if ( ! SkipTabsAndSpaces( fileRef, ioBuf ) ) return false; + noOfIntegers++; + if ( IsNewline ( *ioBuf.ptr ) ) break; + } + if (noOfIntegers!=4) + return false; + format=kXMP_EPSFile; + return true; + } + + } //Found "%%BoundingBox:" + + } // Outer marker loop. + } + default: + { + return false; + } + } + return true; +} + + +// ================================================================================================= +// PostScript_Support::IsSFDFilterUsed +// ================================= +// +// Determines Whether the metadata is embedded using the Sub-FileDecode Approach or no +// In case of Sub-FileDecode filter approach the metaData can be easily extended without +// the need to inject a new XMP packet before the existing Packet. +// +bool PostScript_Support::IsSFDFilterUsed(XMP_IO* &fileRef, XMP_Int64 xpacketOffset) +{ + IOBuffer ioBuf; + fileRef->Rewind(); + fileRef->Seek((xpacketOffset/kIOBufferSize)*kIOBufferSize,kXMP_SeekFromStart); + if ( ! CheckFileSpace ( fileRef, &ioBuf,xpacketOffset%kIOBufferSize ) ) return false; + ioBuf.ptr+=(xpacketOffset%kIOBufferSize); + //skip white spaces + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (!IsWhitespace(*ioBuf.ptr)) break; + --ioBuf.ptr; + } + std::string temp; + bool filterFound=false; + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (*ioBuf.ptr==')') + { + --ioBuf.ptr; + while(true) + { + //get the string till '(' + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false ; + temp+=*ioBuf.ptr; + --ioBuf.ptr; + if (*ioBuf.ptr=='(') + { + if(filterFound) + { + reverse(temp.begin(), temp.end()); + if(!temp.compare("SubFileDecode")) + return true; + } + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false ; + --ioBuf.ptr; + temp.clear(); + break; + } + } + + filterFound=false; + } + else if(*ioBuf.ptr=='[') + { + //end of SubFileDecode Filter parsing + return false; + } + else if(*ioBuf.ptr=='k' ) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; + if(IsWhitespace(*(ioBuf.ptr-4))&& *(ioBuf.ptr-3)=='m' + && *(ioBuf.ptr-2)=='a' && *(ioBuf.ptr-1)=='r' ) + //end of SubFileDecode Filter parsing + return false; + while(true)//ignore till any special mark + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; + if (IsWhitespace(*(ioBuf.ptr))||*(ioBuf.ptr)=='['|| + *(ioBuf.ptr)=='<' ||*(ioBuf.ptr)=='>') break; + --ioBuf.ptr; + } + filterFound=false; + } + else if(*ioBuf.ptr=='<') + { + --ioBuf.ptr; + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (*(ioBuf.ptr)=='<') + { + //end of SubFileDecode Filter parsing + return false; + } + while(true)//ignore till any special mark + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; + if (IsWhitespace(*(ioBuf.ptr))||*(ioBuf.ptr)=='['|| + *(ioBuf.ptr)=='<' ||*(ioBuf.ptr)=='>') break; + --ioBuf.ptr; + } + filterFound=false; + } + else if(*ioBuf.ptr=='>') + { + --ioBuf.ptr; + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (*(ioBuf.ptr)=='>')//ignore the dictionary + { + --ioBuf.ptr; + XMP_Int16 count=1; + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 2 ) ) return false; + if(*(ioBuf.ptr)=='<' && *(ioBuf.ptr-1)=='<') + { + count--; + ioBuf.ptr-=2; + } + else if(*(ioBuf.ptr)=='>' && *(ioBuf.ptr-1)=='>') + { + count++; + ioBuf.ptr-=2; + } + else + { + ioBuf.ptr-=1; + } + if(count==0) + break; + } + } + filterFound=false; + } + else + { + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + temp+=(*ioBuf.ptr); + --ioBuf.ptr; + if (*ioBuf.ptr=='/') + { + if(filterFound) + { + reverse(temp.begin(), temp.end()); + if(!temp.compare("SubFileDecode")) + return true; + } + temp.clear(); + filterFound=false; + break; + } + else if(IsWhitespace(*ioBuf.ptr)) + { + reverse(temp.begin(), temp.end()); + if(!temp.compare("filter")&&!filterFound) + filterFound=true; + else + filterFound=false; + temp.clear(); + break; + } + } + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + --ioBuf.ptr; + } + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (!IsWhitespace(*ioBuf.ptr)) break; + --ioBuf.ptr; + } + + } + return false; + +} + + +// ================================================================================================= +// constructDateTime +// ================================= +// +// If input string date is of the format D:YYYYMMDDHHmmSSOHH'mm' and valid +// output format YYYY-MM-DDThh:mm:ssTZD is returned +// +static void constructDateTime(const std::string &input,std::string& outDate) +{ + std::string date; + XMP_DateTime datetime; + std::string verdate; + size_t start =0; + if(input[0]=='D' && input[1]==':') + start=2; + if (input.length() >=14+start) + { + for(int x=0;x<4;x++) + { + date+=input[start+x];//YYYY + } + date+='-'; + start+=4; + for(int x=0;x<2;x++) + { + date+=input[start+x];//MM + } + date+='-'; + start+=2; + for(int x=0;x<2;x++) + { + date+=input[start+x];//DD + } + date+='T'; + start+=2; + for(int x=0;x<2;x++) + { + date+=input[start+x];//HH + } + + date+=':'; + start+=2; + for(int x=0;x<2;x++) + { + date+=input[start+x];//MM + } + + date+=':'; + start+=2; + for(int x=0;x<2;x++) + { + date+=input[start+x];//SS + } + start+=2; + if ((input[start]=='+' || input[start]=='-' )&&input.length() ==19+start) + { + date+=input[start]; + start++; + for(int x=0;x<2;x++) + { + date+=input[start+x];//hh + } + date+=':'; + start+=2; + for(int x=0;x<2;x++) + { + date+=input[start+x];//mm + } + } + else + { + date+='Z'; + } + + try + { + SXMPUtils::ConvertToDate ( date.c_str(),&datetime); + SXMPUtils::ConvertFromDate (datetime,&verdate); + } + catch(...) + { + return; + } + outDate=verdate; + } + +} + +// ================================================================================================= +// GetNumber +// ========= +// +// Extracts number from a char string +// +static short GetNumber(const char **inString,short noOfChars=SHRT_MAX) +{ + const char* inStr=*inString; + short number=0; + while(IsNumeric ( *inStr )&& noOfChars--) + { + number=number*10 +inStr[0]-'0'; + inStr++; + } + *inString=inStr; + return number; +} + +// ================================================================================================= +// tokeniseDateString +// ================== +// +// Parses Date string and tokenizes it for extracting different parts of a date +// +static bool tokeniseDateString(const char* inString,std::vector<PostScript_Support::DateTimeTokens> &tokens ) +{ + const char* inStr= inString; + PostScript_Support::DateTimeTokens dttoken; + //skip leading whitespace + while ( inStr[0]!='\0' ) + { + while( IsSpaceOrTab ( *inStr ) || *inStr =='(' + ||*inStr ==')' ||*inStr==',') + { + ++inStr; + } + if (*inStr=='\0') return true; + dttoken=PostScript_Support::DateTimeTokens(); + if (IsNumeric(*inStr)) + { + while(IsNumeric(*inStr)||(IsDelimiter(*inStr)&&dttoken.noOfDelimiter==0)|| + (dttoken.delimiter==*inStr && dttoken.noOfDelimiter!=0)) + { + if (IsDelimiter(*inStr)) + { + dttoken.delimiter=inStr[0]; + dttoken.noOfDelimiter++; + } + dttoken.token+=*(inStr++); + } + tokens.push_back(dttoken); + } + else if (IsAlpha(*inStr)) + { + if(*inStr=='D'&& *(inStr+1)==':') + { + inStr+=2; + while( IsNumeric(*inStr)) + { + dttoken.token+=*(inStr++); + } + } + else + { + while( IsAlpha(*inStr)) + { + dttoken.token+=*(inStr++); + } + + } + tokens.push_back(dttoken); + } + else if (*inStr=='-' ||*inStr=='+') + { + dttoken.token+=*(inStr++); + while( IsNumeric(*inStr)||*inStr==':') + { + if (*inStr==':') + { + dttoken.delimiter=inStr[0]; + dttoken.noOfDelimiter++; + } + dttoken.token+=*(inStr++); + } + tokens.push_back(dttoken); + } + else + { + ++inStr; + } + } + return true; +} + +// ================================================================================================= +// SwapMonthDateIfNeeded +// ===================== +// +// Swaps month and date value if it creates a possible valid date +// +static void SwapMonthDateIfNeeded(short &day, short&month) +{ + if(month>12&& day<13) + { + short temp=month; + month=day; + day=temp; + } +} + +// ================================================================================================= +// AdjustYearIfNeeded +// ===================== +// +// Guess the year for a two digit year in a date +// +static void AdjustYearIfNeeded(short &year) +{ + if (year<100) + { + if (year >40) + { + year=1900+year; + } + else + { + year=2000+year; + } + } +} + +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. +} +// ================================================================================================= +// PostScript_Support::ConvertToDate +// ================================= +// +// Converts date string from native of Postscript to YYYY-MM-DDThh:mm:ssTZD if a valid date is identified +// +std::string PostScript_Support::ConvertToDate(const char* inString) +{ + PostScript_Support::Date date(0,0,0,0,0,0); + std::string dateTimeString; + short number=0; + std::vector<PostScript_Support::DateTimeTokens> tokenzs; + tokeniseDateString(inString,tokenzs ); + std::vector<PostScript_Support::DateTimeTokens>:: const_iterator itr=tokenzs.begin(); + for(;itr!=tokenzs.end();itr++) + { + if(itr->token[0]=='+' ||itr->token[0]=='-') + { + const char *str=itr->token.c_str(); + date.offsetSign=*(str++); + date.offsetHour=GetNumber(&str,2); + if (*str==':')str++; + date.offsetMin=GetNumber(&str,2); + if (!(date.offsetHour<=12 && date.offsetHour>=0 + &&date.offsetMin>=0 && date.offsetMin<=59)) + { + date.offsetSign='+'; + date.offsetHour=0; + date.offsetMin=0; + } + else + { + date.containsOffset= true; + } + } + else if(itr->noOfDelimiter!=0) + {//either a date or time token + if(itr->noOfDelimiter==2 && itr->delimiter=='/') + { + if(date.day==0&& date.month==0 && date.year==0) + { + const char *str=itr->token.c_str(); + number=GetNumber(&str); + str++; + if (number<32) + { + date.month=number; + date.day=GetNumber(&str); + SwapMonthDateIfNeeded(date.day, date.month); + str++; + date.year=GetNumber(&str); + AdjustYearIfNeeded(date.year); + } + else + { + date.year=number; + AdjustYearIfNeeded(date.year); + date.month=GetNumber(&str); + str++; + date.day=GetNumber(&str); + SwapMonthDateIfNeeded(date.day, date.month); + } + } + } + else if(itr->noOfDelimiter==2 && itr->delimiter==':') + { + const char *str=itr->token.c_str(); + date.hours=GetNumber(&str); + str++; + date.minutes=GetNumber(&str); + str++; + date.seconds=GetNumber(&str); + if (date.hours>23|| date.minutes>59|| date.seconds>59) + { + date.hours=0; + date.minutes=0; + date.seconds=0; + } + } + else if(itr->noOfDelimiter==1 && itr->delimiter==':') + { + const char *str=itr->token.c_str(); + date.hours=GetNumber(&str); + str++; + date.minutes=GetNumber(&str); + if (date.hours>23|| date.minutes>59) + { + date.hours=0; + date.minutes=0; + } + } + else if(itr->noOfDelimiter==2 && itr->delimiter=='-') + { + const char *str=itr->token.c_str(); + number=GetNumber(&str); + str++; + if (number>31) + { + date.year=number; + AdjustYearIfNeeded(date.year); + date.month=GetNumber(&str); + str++; + date.day=GetNumber(&str); + SwapMonthDateIfNeeded(date.day, date.month); + } + else + { + date.month=number; + date.day=GetNumber(&str); + str++; + SwapMonthDateIfNeeded(date.day, date.month); + date.year=GetNumber(&str); + AdjustYearIfNeeded(date.year); + } + } + else if(itr->noOfDelimiter==2 && itr->delimiter=='.') + { + const char *str=itr->token.c_str(); + date.year=GetNumber(&str); + str++; + AdjustYearIfNeeded(date.year); + date.month=GetNumber(&str); + str++; + date.day=GetNumber(&str); + SwapMonthDateIfNeeded(date.day, date.month); + + } + } + else if (IsAlpha(itr->token[0])) + { //this can be a name of the Month + // name of the day is ignored + short month=0; + std::string lowerToken=itr->token; + std::transform(lowerToken.begin(), lowerToken.end(), lowerToken.begin(), ::tolower); + if(!lowerToken.compare("jan")||!lowerToken.compare("january")) + { + month=1; + } + else if (!lowerToken.compare("feb")||!lowerToken.compare("february")) + { + month=2; + } + else if (!lowerToken.compare("mar")||!lowerToken.compare("march")) + { + month=3; + } + else if (!lowerToken.compare("apr")||!lowerToken.compare("april")) + { + month=4; + } + else if (!lowerToken.compare("may")) + { + month=5; + } + else if (!lowerToken.compare("jun")||!lowerToken.compare("june")) + { + month=6; + } + else if (!lowerToken.compare("jul")||!lowerToken.compare("july")) + { + month=7; + } + else if (!lowerToken.compare("aug")||!lowerToken.compare("august")) + { + month=8; + } + else if (!lowerToken.compare("sep")||!lowerToken.compare("september")) + { + month=9; + } + else if (!lowerToken.compare("oct")||!lowerToken.compare("october")) + { + month=10; + } + else if (!lowerToken.compare("nov")||!lowerToken.compare("november")) + { + month=11; + } + else if (!lowerToken.compare("dec")||!lowerToken.compare("december")) + { + month=12; + } + else if (!lowerToken.compare("pm")) + { + if (date.hours<13) + { + date.hours+=12; + } + } + else if (itr->token.length()>14) + { + constructDateTime(itr->token,dateTimeString); + } + if(month!=0 && date.month==0) + { + date.month=month; + if(date.day==0) + { + if(itr!=tokenzs.begin()) + { + --itr; + if (itr->noOfDelimiter==0 && IsNumeric(itr->token[0]) ) + { + const char * str=itr->token.c_str(); + short day= GetNumber(&str); + if (day<=31 &&day >=1) + { + date.day=day; + } + } + ++itr; + } + if(itr!=tokenzs.end()) + { + ++itr; + if (itr!=tokenzs.end()&&itr->noOfDelimiter==0 && IsNumeric(itr->token[0]) ) + { + const char * str=itr->token.c_str(); + short day= GetNumber(&str); + if (day<=31 &&day >=1) + { + date.day=day; + } + } + } + } + } + } + else if (IsNumeric(itr->token[0]) && date.year==0&&itr->token.length()==4) + {//this is the year + const char * str=itr->token.c_str(); + date.year=GetNumber(&str); + } + else if (itr->token.length()>=14) + { + constructDateTime(itr->token,dateTimeString); + } + } + if (dateTimeString.length()==0) + { + char dtstr[100]; + XMP_DateTime datetime; + if ( date.year < 10000 && date.month < 13 && date.month > 0 && date.day > 0 ) + { + bool isValidDate=true; + if ( date.month == 2 ) + { + if ( IsLeapYear ( date.year ) ) + { + if ( date.day > 29 ) isValidDate=false; + } + else + { + if ( date.day > 28 ) isValidDate=false; + } + } + else if ( date.month == 4 || date.month == 6 || date.month == 9 + || date.month == 11 ) + { + if ( date.day > 30 ) isValidDate=false; + } + else + { + if ( date.day > 31 ) isValidDate=false; + } + if( isValidDate && ! ( date == PostScript_Support::Date ( 0, 0, 0, 0, 0, 0 ) ) ) + { + if ( date.containsOffset ) + { + sprintf(dtstr,"%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",date.year,date.month,date.day, + date.hours,date.minutes,date.seconds,date.offsetSign,date.offsetHour,date.offsetMin); + } + else + { + sprintf(dtstr,"%04d-%02d-%02dT%02d:%02d:%02dZ",date.year,date.month,date.day, + date.hours,date.minutes,date.seconds); + } + try + { + + SXMPUtils::ConvertToDate ( dtstr, &datetime ) ; + SXMPUtils::ConvertFromDate ( datetime, &dateTimeString ) ; + } + catch(...) + { + } + } + } + } + + return dateTimeString; +} +// ================================================================================================= diff --git a/XMPFiles/source/FormatSupport/PostScript_Support.hpp b/XMPFiles/source/FormatSupport/PostScript_Support.hpp new file mode 100644 index 0000000..688fa6d --- /dev/null +++ b/XMPFiles/source/FormatSupport/PostScript_Support.hpp @@ -0,0 +1,266 @@ +#ifndef __PostScript_Support_hpp__ +#define __PostScript_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2012 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/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + + +#define MAX_NO_MARK 100 +#define IsNumeric( ch ) ( ch >='0' && ch<='9' ) +#define Uns8Ptr(p) ((XMP_Uns8 *) (p)) +#define IsPlusMinusSign(ch) ( ch =='+' || ch=='-' ) +#define IsDateDelimiter( ch ) ( ((ch) == '/') || ((ch) == '-') || ((ch) == '.') ) +#define IsTimeDelimiter( ch ) ( ((ch) == ':') ) +#define IsDelimiter(ch) (IsDateDelimiter( ch ) || IsTimeDelimiter( ch )) +#define IsAlpha(ch) ((ch>=97 &&ch <=122) || (ch>=65 && ch<=91)) + + +enum { + kPSHint_NoMarker = 0, + kPSHint_NoMain = 1, + kPSHint_MainFirst = 2, + kPSHint_MainLast = 3 +}; + +enum UpdateMethod{ + kPS_None = 0, + kPS_Inplace = 1, + kPS_ExpandSFDFilter = 2, + kPS_InjectNew = 3 +}; + +enum TokenFlag{ + // -------------------- + // Flags for native Metadata and DSC commnents in EPS format + + /// No Native MetaData + kPS_NoData = 0x00000001, + /// Document Creator tool + kPS_CreatorTool = 0x00000002, + /// Document Creation Date + kPS_CreateDate = 0x00000004, + /// Document Modify Date + kPS_ModifyDate = 0x00000008, + /// Document Creator/Author + kPS_Creator = 0x00000010, + /// Document Title + kPS_Title = 0x00000020, + /// Document Desciption + kPS_Description = 0x00000040, + /// Document Subject/Keywords + kPS_Subject = 0x00000080, + /// ADO_ContainsXMP hint + kPS_ADOContainsXMP = 0x00000100, + /// End Comments + kPS_EndComments = 0x00000200, + /// Begin Prolog + kPS_BeginProlog = 0x00000400, + /// End Prolog + kPS_EndProlog = 0x00000800, + /// Begin Setup + kPS_BeginSetup = 0x00001000, + /// End Setup + kPS_EndSetup = 0x00002000, + /// Page + kPS_Page = 0x00004000, + /// End Page Comments + kPS_EndPageComments = 0x00008000, + /// Begin Page SetUp + kPS_BeginPageSetup = 0x00010000, + /// End Page SetUp + kPS_EndPageSetup = 0x00020000, + /// Trailer + kPS_Trailer = 0x00040000, + /// EOF + kPS_EOF = 0x00080000, + /// End PostScript + kPS_EndPostScript = 0x00100000, + /// Max Token + kPS_MaxToken = 0x00200000 +}; + +enum NativeMetadataIndex{ + // -------------------- + // Index native Metadata ina PS file + kPS_dscCreator = 0, + kPS_dscCreateDate = 1, + kPS_dscFor = 2, + kPS_dscTitle = 3, + kPS_docInfoCreator = 4, + kPS_docInfoCreateDate = 5, + kPS_docInfoModDate = 6, + kPS_docInfoAuthor = 7, + kPS_docInfoTitle = 8, + kPS_docInfoSubject = 9, + kPS_docInfoKeywords = 10, + kPS_MaxNativeIndexValue +}; + +static XMP_Uns64 nativeIndextoFlag[]={ kPS_CreatorTool, + kPS_CreateDate, + kPS_Creator, + kPS_Title, + kPS_CreatorTool, + kPS_CreateDate, + kPS_ModifyDate, + kPS_Creator, + kPS_Title, + kPS_Description, + kPS_Subject + }; + +static const std::string kPSFileTag = "%!PS-Adobe-"; +static const std::string kPSContainsXMPString = "%ADO_ContainsXMP:"; +static const std::string kPSContainsBBoxString = "%%BoundingBox:"; +static const std::string kPSContainsBeginDocString = "%%BeginDocument:"; +static const std::string kPSContainsEndDocString = "%%EndDocument"; +static const std::string kPSContainsTrailerString = "%%Trailer"; +static const std::string kPSContainsCreatorString = "%%Creator:"; +static const std::string kPSContainsCreateDateString = "%%CreationDate:"; +static const std::string kPSContainsForString = "%%For:"; +static const std::string kPSContainsTitleString = "%%Title:"; +static const std::string kPSContainsAtendString = "(atend)"; +static const std::string kPSEndCommentString = "%%EndComments"; // ! Assumed shorter than kPSContainsXMPString. +static const std::string kPSContainsDocInfoString = "/DOCINFO"; +static const std::string kPSContainsPdfmarkString = "pdfmark"; +static const std::string kPS_XMPHintMainFirst="%ADO_ContainsXMP: MainFirst\n"; +static const std::string kPS_XMPHintMainLast="%ADO_ContainsXMP: MainLast\n"; + +// For new xpacket injection into the EPS file is done in Postscript using the pdfmark operator +// There are different conventions described for EPS and PS files in XMP Spec part 3. +// The tokens kEPS_Injectdata1, kEPS_Injectdata2 and kEPS_Injectdata3 are used to +// embedd xpacket in EPS files.the xpacket is written inbetween kEPS_Injectdata1 and kEPS_Injectdata2. +// The tokens kPS_Injectdata1 and kPS_Injectdata2 are used to embedd xpacket in DSC compliant PS files +// The code inside the tokens is taken from examples in XMP Spec part 3 +// section 2.6.2 PS, EPS (PostScript® and Encapsulated PostScript) +static const std::string kEPS_Injectdata1="\n/currentdistillerparams where\n" +"{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse\n" +"{userdict /EPSHandler1_pdfmark /cleartomark load put\n" +"userdict /EPSHandler1_ReadMetadata_pdfmark {flushfile cleartomark} bind put}\n" +"{ userdict /EPSHandler1_pdfmark /pdfmark load put\n" +"userdict /EPSHandler1_ReadMetadata_pdfmark {/PUT pdfmark} bind put } ifelse\n" +"[/NamespacePush EPSHandler1_pdfmark\n" +"[/_objdef {eps_metadata_stream} /type /stream /OBJ EPSHandler1_pdfmark\n" +"[{eps_metadata_stream} 2 dict begin\n" +"/Type /Metadata def /Subtype /XML def currentdict end /PUT EPSHandler1_pdfmark\n" +"[{eps_metadata_stream}\n" +"currentfile 0 (% &&end EPS XMP packet marker&&)\n" +"/SubFileDecode filter EPSHandler1_ReadMetadata_pdfmark\n"; + +static const std::string kPS_Injectdata1="\n/currentdistillerparams where\n" +"{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse\n" +"{userdict /PSHandler1_pdfmark /cleartomark load put\n" +"userdict /PSHandler1_ReadMetadata_pdfmark {flushfile cleartomark} bind put}\n" +"{ userdict /PSHandler1_pdfmark /pdfmark load put\n" +"userdict /PSHandler1_ReadMetadata_pdfmark {/PUT pdfmark} bind put } ifelse\n" +"[/NamespacePush PSHandler1_pdfmark\n" +"[/_objdef {ps_metadata_stream} /type /stream /OBJ PSHandler1_pdfmark\n" +"[{ps_metadata_stream} 2 dict begin\n" +"/Type /Metadata def /Subtype /XML def currentdict end /PUT PSHandler1_pdfmark\n" +"[{ps_metadata_stream}\n" +"currentfile 0 (% &&end PS XMP packet marker&&)\n" +"/SubFileDecode filter PSHandler1_ReadMetadata_pdfmark\n"; + +static const std::string kEPS_Injectdata2="\n% &&end EPS XMP packet marker&&\n" +"[/Document\n" +"1 dict begin /Metadata {eps_metadata_stream} def\n" +"currentdict end /BDC EPSHandler1_pdfmark\n" +"[/NamespacePop EPSHandler1_pdfmark\n"; + + +static const std::string kPS_Injectdata2="\n% &&end PS XMP packet marker&&\n" +"[{Catalog} {ps_metadata_stream} /Metadata PSHandler1_pdfmark\n" +"[/NamespacePop PSHandler1_pdfmark\n"; + +static const std::string kEPS_Injectdata3="\n/currentdistillerparams where\n" + "{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse\n" + "{userdict /EPSHandler1_pdfmark /cleartomark load put}\n" + "{ userdict /EPSHandler1_pdfmark /pdfmark load put} ifelse\n" + "[/EMC EPSHandler1_pdfmark\n"; + + +namespace PostScript_Support +{ + struct Date + { + short day; + short month; + short year; + short hours; + short minutes; + short seconds; + bool containsOffset; + char offsetSign; + short offsetHour; + short offsetMin; + Date(short pday=1,short pmonth=1,short pyear=1900,short phours=0, + short pminutes=0,short pseconds=0):day(pday),month(pmonth), + year(pyear),hours(phours),minutes(pminutes),seconds(pseconds), + containsOffset(false),offsetSign('+'),offsetHour(0),offsetMin(0) + { + } + bool operator==(const Date &a) + { + return this->day==a.day && + this->month==a.month && + this->year==a.year && + this->hours==a.hours && + this->minutes==a.minutes && + this->seconds==a.seconds && + this->containsOffset==a.containsOffset && + this->offsetSign==a.offsetSign && + this->offsetHour==a.offsetHour && + this->offsetMin==a.offsetMin; + } + }; + struct DateTimeTokens + { + std::string token; + short noOfDelimiter; + char delimiter; + DateTimeTokens(std::string ptoken="",short pnoOfDelimiter=0,char pdelimiter=0): + token(ptoken),noOfDelimiter(pnoOfDelimiter),delimiter(pdelimiter) + { + } + }; + + //function to parse strings and get date out of it + std::string ConvertToDate(const char* inString); + // These helpers are similar to RefillBuffer and CheckFileSpace with the difference that the it traverses + // the file stream in reverse order + void RevRefillBuffer ( XMP_IO* fileRef, IOBuffer* ioBuf ); + bool RevCheckFileSpace ( XMP_IO* fileRef, IOBuffer* ioBuf, size_t neededLen ); + + // function to detect character codes greater than 127 in a string + bool HasCodesGT127(const std::string & value); + + // function moves the file pointer ahead such that it skips all tabs and spaces + bool SkipTabsAndSpaces(XMP_IO* file,IOBuffer& ioBuf); + + // function moves the file pointer ahead such that it skips all characters until a newline + bool SkipUntilNewline(XMP_IO* file,IOBuffer& ioBuf); + + // function to detect character codes greater than 127 in a string + bool IsValidPSFile(XMP_IO* fileRef,XMP_FileFormat &format); + + // Determines Whether the metadata is embedded using the Sub-FileDecode Approach or no + bool IsSFDFilterUsed(XMP_IO* &fileRef, XMP_Int64 xpacketOffset); + +} // namespace PostScript_Support + +#endif // __PostScript_Support_hpp__ diff --git a/XMPFiles/source/FormatSupport/QuickTime_Support.cpp b/XMPFiles/source/FormatSupport/QuickTime_Support.cpp index 5a33ab4..5dde0c5 100644 --- a/XMPFiles/source/FormatSupport/QuickTime_Support.cpp +++ b/XMPFiles/source/FormatSupport/QuickTime_Support.cpp @@ -22,6 +22,7 @@ #include "source/UnicodeInlines.incl_cpp" #include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" #include "source/XIO.hpp" +#include "source/EndianUtils.hpp" // ================================================================================================= diff --git a/XMPFiles/source/FormatSupport/RIFF.hpp b/XMPFiles/source/FormatSupport/RIFF.hpp index 533d9a3..e2451e5 100644 --- a/XMPFiles/source/FormatSupport/RIFF.hpp +++ b/XMPFiles/source/FormatSupport/RIFF.hpp @@ -174,7 +174,11 @@ namespace RIFF { // ================================================================================================= // ImportCr8rItems // =============== +#if SUNOS_SPARC || SUNOS_X86 + #pragma pack ( 1 ) +#else #pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 struct PrmLBoxContent { XMP_Uns32 magic; XMP_Uns32 size; @@ -199,7 +203,11 @@ namespace RIFF { char appOptions[16]; char appName[32]; }; +#if SUNOS_SPARC || SUNOS_X86 + #pragma pack ( ) +#else #pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 // static getter, determines appropriate chunkType (peeking)and returns // the respective constructor. It's the caller's responsibility to diff --git a/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp b/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp index 225c91f..5aa4435 100644 --- a/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp +++ b/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp @@ -19,6 +19,8 @@ #define snprintf _snprintf #endif +#include "source/EndianUtils.hpp" + #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' @@ -387,7 +389,7 @@ ImportSingleTIFF_Short ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ { try { // Don't let errors with one stop the others. - XMP_Uns16 binValue = *((XMP_Uns16*)tagInfo.dataPtr); + XMP_Uns16 binValue = GetUns16AsIs ( tagInfo.dataPtr ); if ( ! nativeEndian ) binValue = Flip2 ( binValue ); char strValue[20]; @@ -412,7 +414,7 @@ ImportSingleTIFF_Long ( const TIFF_Manager::TagInfo & tagInfo, const bool native { try { // Don't let errors with one stop the others. - XMP_Uns32 binValue = *((XMP_Uns32*)tagInfo.dataPtr); + XMP_Uns32 binValue = GetUns32AsIs ( tagInfo.dataPtr ); if ( ! nativeEndian ) binValue = Flip4 ( binValue ); char strValue[20]; @@ -438,8 +440,8 @@ ImportSingleTIFF_Rational ( const TIFF_Manager::TagInfo & tagInfo, const bool na 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]; + XMP_Uns32 binNum = GetUns32AsIs ( &binPtr[0] ); + XMP_Uns32 binDenom = GetUns32AsIs ( &binPtr[1] ); if ( ! nativeEndian ) { binNum = Flip4 ( binNum ); binDenom = Flip4 ( binDenom ); @@ -467,9 +469,14 @@ ImportSingleTIFF_SRational ( const TIFF_Manager::TagInfo & tagInfo, const bool n { 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 SUNOS_SPARC + XMP_Uns32 binPtr[2]; + memcpy(&binPtr, tagInfo.dataPtr, sizeof(XMP_Uns32)*2); +#else + XMP_Uns32 * binPtr = (XMP_Uns32*)tagInfo.dataPtr; +#endif //#if SUNOS_SPARC + XMP_Int32 binNum = GetUns32AsIs ( &binPtr[0] ); + XMP_Int32 binDenom = GetUns32AsIs ( &binPtr[1] ); if ( ! nativeEndian ) { Flip4 ( &binNum ); Flip4 ( &binDenom ); @@ -537,7 +544,7 @@ ImportSingleTIFF_Byte ( const TIFF_Manager::TagInfo & tagInfo, XMP_Uns8 binValue = *((XMP_Uns8*)tagInfo.dataPtr); char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. + snprintf ( strValue, sizeof(strValue), "%hu", (XMP_Uns16)binValue ); // AUDIT: Using sizeof(strValue) is safe. xmp->SetProperty ( xmpNS, xmpProp, strValue ); @@ -561,7 +568,7 @@ ImportSingleTIFF_SByte ( const TIFF_Manager::TagInfo & tagInfo, XMP_Int8 binValue = *((XMP_Int8*)tagInfo.dataPtr); char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. + snprintf ( strValue, sizeof(strValue), "%hd", (short)binValue ); // AUDIT: Using sizeof(strValue) is safe. xmp->SetProperty ( xmpNS, xmpProp, strValue ); @@ -815,8 +822,8 @@ ImportArrayTIFF_Rational ( const TIFF_Manager::TagInfo & tagInfo, const bool nat for ( size_t i = 0; i < tagInfo.count; ++i, binPtr += 2 ) { - XMP_Uns32 binNum = binPtr[0]; - XMP_Uns32 binDenom = binPtr[1]; + XMP_Uns32 binNum = GetUns32AsIs ( &binPtr[0] ); + XMP_Uns32 binDenom = GetUns32AsIs ( &binPtr[1] ); if ( ! nativeEndian ) { binNum = Flip4 ( binNum ); binDenom = Flip4 ( binDenom ); @@ -936,7 +943,7 @@ ImportArrayTIFF_Byte ( const TIFF_Manager::TagInfo & tagInfo, XMP_Uns8 binValue = *binPtr; char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. + snprintf ( strValue, sizeof(strValue), "%hu", (XMP_Uns16)binValue ); // AUDIT: Using sizeof(strValue) is safe. xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); @@ -968,7 +975,7 @@ ImportArrayTIFF_SByte ( const TIFF_Manager::TagInfo & tagInfo, XMP_Int8 binValue = *binPtr; char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. + snprintf ( strValue, sizeof(strValue), "%hd", (short)binValue ); // AUDIT: Using sizeof(strValue) is safe. xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); @@ -1420,7 +1427,7 @@ ImportTIFF_Flash ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, { try { // Don't let errors with one stop the others. - XMP_Uns16 binValue = *((XMP_Uns16*)tagInfo.dataPtr); + XMP_Uns16 binValue = GetUns16AsIs ( tagInfo.dataPtr ); if ( ! nativeEndian ) binValue = Flip2 ( binValue ); bool fired = (bool)(binValue & 1); // Avoid implicit 0/1 conversion. @@ -1559,7 +1566,7 @@ ImportTIFF_CFATable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, 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. + snprintf ( buffer, sizeof(buffer), "%hu", (XMP_Uns16)(*bytePtr) ); // AUDIT: Use of sizeof(buffer) is safe. xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, buffer ); } @@ -1679,14 +1686,15 @@ ImportTIFF_GPSCoordinate ( const TIFF_Manager & tiff, const TIFF_Manager::TagInf secDenom = Flip4 ( secDenom ); } - degNum = binPtr[0]; - degDenom = binPtr[1]; + degNum = GetUns32AsIs ( &binPtr[0] ); + degDenom = GetUns32AsIs ( &binPtr[1] ); + if ( posInfo.count >= 2 ) { - minNum = binPtr[2]; - minDenom = binPtr[3]; + minNum = GetUns32AsIs ( &binPtr[2] ); + minDenom = GetUns32AsIs ( &binPtr[3] ); if ( posInfo.count >= 3 ) { - secNum = binPtr[4]; - secDenom = binPtr[5]; + secNum = GetUns32AsIs ( &binPtr[4] ); + secDenom = GetUns32AsIs ( &binPtr[5] ); } } @@ -1790,12 +1798,12 @@ ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo 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]; + XMP_Uns32 hourNum = GetUns32AsIs ( &binPtr[0] ); + XMP_Uns32 hourDenom = GetUns32AsIs ( &binPtr[1] ); + XMP_Uns32 minNum = GetUns32AsIs ( &binPtr[2] ); + XMP_Uns32 minDenom = GetUns32AsIs ( &binPtr[3] ); + XMP_Uns32 secNum = GetUns32AsIs ( &binPtr[4] ); + XMP_Uns32 secDenom = GetUns32AsIs ( &binPtr[5] ); if ( ! nativeEndian ) { hourNum = Flip4 ( hourNum ); hourDenom = Flip4 ( hourDenom ); @@ -2116,7 +2124,8 @@ PhotoDataUtils::Import2WayExif ( const TIFF_Manager & exif, SXMPMeta * xmp, int 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); + + *((XMP_Uns32*)str) = GetUns32AsIs ( tagInfo.dataPtr ); str[4] = 0; xmp->SetProperty ( kXMP_NS_EXIF, "ExifVersion", str ); } @@ -2125,7 +2134,8 @@ PhotoDataUtils::Import2WayExif ( const TIFF_Manager & exif, SXMPMeta * xmp, int 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); + + *((XMP_Uns32*)str) = GetUns32AsIs ( tagInfo.dataPtr ); str[4] = 0; xmp->SetProperty ( kXMP_NS_EXIF, "FlashpixVersion", str ); } diff --git a/XMPFiles/source/FormatSupport/SWF_Support.cpp b/XMPFiles/source/FormatSupport/SWF_Support.cpp index ec7b9a6..5ff80f1 100644 --- a/XMPFiles/source/FormatSupport/SWF_Support.cpp +++ b/XMPFiles/source/FormatSupport/SWF_Support.cpp @@ -127,7 +127,7 @@ XMP_Int64 SWF_IO::DecompressFileToMemory ( XMP_IO * fileIn, RawDataBlock * dataO XMP_Int32 ioCount; XMP_Int64 offsetIn; const XMP_Int64 lengthIn = fileIn->Length(); - XMP_Enforce ( (SWF_IO::HeaderPrefixSize <= lengthIn) && (lengthIn <= SWF_IO::MaxExpandedSize) ); + XMP_Enforce ( ((XMP_Int64)SWF_IO::HeaderPrefixSize <= lengthIn) && (lengthIn <= SWF_IO::MaxExpandedSize) ); // Set the uncompressed part of the header. Save the expanded size from the file. diff --git a/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp b/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp index 40cb9bd..a3bf808 100644 --- a/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp +++ b/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp @@ -11,8 +11,11 @@ #include "public/include/XMP_Const.h" #include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" + #include "source/XIO.hpp" +#include "source/EndianUtils.hpp" + // ================================================================================================= /// \file TIFF_FileWriter.cpp /// \brief TIFF_FileWriter is used for memory-based read-write access and all file-based access. @@ -586,15 +589,26 @@ void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bo 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. + // Find and process the primary, thumbnail, Exif, GPS, and Interoperability IFDs. XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( this->memStream, length ); - if ( primaryIFDOffset != 0 ) (void) this->ProcessMemoryIFD ( primaryIFDOffset, kTIFF_PrimaryIFD ); + if ( primaryIFDOffset != 0 ) { + XMP_Uns32 tnailOffset = this->ProcessMemoryIFD ( primaryIFDOffset, kTIFF_PrimaryIFD ); + if ( tnailOffset != 0 ) { + if ( IsOffsetValid ( tnailOffset, 8, ifdLimit ) ) { // Remove a bad Thumbnail IFD Offset + ( void ) this->ProcessMemoryIFD ( tnailOffset, kTIFF_TNailIFD ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); + this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_TNailIFD ); + } + } + } const InternalTagInfo* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->dataLen == 4) ) { @@ -605,9 +619,11 @@ void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bo 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. + if ( IsOffsetValid ( gpsOffset, 8, ifdLimit ) ) { // Remove a bad GPS IFD offset. (void) this->ProcessMemoryIFD ( gpsOffset, kTIFF_GPSInfoIFD ); } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer ); } } @@ -615,9 +631,11 @@ void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bo 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. + if ( IsOffsetValid ( interopOffset, 8, ifdLimit ) ) { // Remove a bad Interoperability IFD offset. (void) this->ProcessMemoryIFD ( interopOffset, kTIFF_InteropIFD ); } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); } } @@ -653,16 +671,22 @@ 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_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); } 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 ( tagCount >= 0x8000 ) { + XMP_Error error ( kXMPErr_BadTIFF, "Outrageous IFD count" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); + } + if ( (XMP_Uns32)(2 + tagCount*12 + 4) > (this->tiffLength - ifdOffset) ) { - XMP_Throw ( "Out of bounds IFD", kXMPErr_BadTIFF ); + XMP_Error error ( kXMPErr_BadTIFF, "Out of bounds IFD" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); } ifdInfo.origIFDOffset = ifdOffset; @@ -682,7 +706,11 @@ XMP_Uns32 TIFF_FileWriter::ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd 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 SUNOS_SPARC + mapTag.smallValue = IE.getUns32(&rawTag->dataOrOffset); +#else + mapTag.smallValue = GetUns32AsIs ( &rawTag->dataOrOffset ); // Keep the value or offset in stream byte ordering. +#endif //#if SUNOS_SPARC if ( mapTag.dataLen <= 4 ) { mapTag.origDataOffset = ifdOffset + 2 + (12 * (XMP_Uns32)i) + 8; // Compute the data offset. @@ -720,39 +748,50 @@ XMP_Uns32 TIFF_FileWriter::ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd 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; + if ( this->tiffLength < 8 ) return; // Ignore empty or impossibly short. + fileRef->Rewind ( ); 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_Uns8 tiffHeader [8]; + fileRef->ReadAll ( tiffHeader, sizeof(tiffHeader) ); + XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( tiffHeader, this->tiffLength ); - XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( ioBuf.ptr, this->tiffLength ); - - if ( primaryIFDOffset != 0 ) (void) this->ProcessFileIFD ( kTIFF_PrimaryIFD, primaryIFDOffset, fileRef, &ioBuf ); + if ( primaryIFDOffset == 0 ) { + return; + } else { + XMP_Uns32 tnailOffset = this->ProcessFileIFD ( kTIFF_PrimaryIFD, primaryIFDOffset, fileRef ); + if ( tnailOffset != 0 ) { + if ( IsOffsetValid ( tnailOffset, 8, ifdLimit ) ) { + ( void ) this->ProcessFileIFD ( kTIFF_TNailIFD, tnailOffset, fileRef ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_Recoverable, error ); + this->DeleteTag( kTIFF_PrimaryIFD, kTIFF_TNailIFD ); + } + } + } 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 ); + (void) this->ProcessFileIFD ( kTIFF_ExifIFD, exifOffset, fileRef ); } 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 ); + if ( IsOffsetValid (gpsOffset, 8, ifdLimit ) ) { // Remove a bad GPS IFD offset. + (void) this->ProcessFileIFD ( kTIFF_GPSInfoIFD, gpsOffset, fileRef ); } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_Recoverable, error ); this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer ); } } @@ -760,9 +799,11 @@ void TIFF_FileWriter::ParseFileStream ( XMP_IO* fileRef ) 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 ); + if ( IsOffsetValid (interopOffset, 8, ifdLimit ) ) { // Remove a bad Interoperability IFD offset. + (void) this->ProcessFileIFD ( kTIFF_InteropIFD, interopOffset, fileRef ); } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_Recoverable, error ); this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); } } @@ -792,25 +833,36 @@ void TIFF_FileWriter::ParseFileStream ( XMP_IO* fileRef ) // ================================================================================================= // TIFF_FileWriter::ProcessFileIFD // =============================== +// +// Each IFD has a UInt16 count of IFD entries, a sequence of 12 byte IFD entries, then a UInt32 +// offset to the next IFD. The integer byte order is determined by the II or MM at the TIFF start. -XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, XMP_IO* fileRef, void* _ioBuf ) +XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, XMP_IO* fileRef ) { - IOBuffer* ioBuf = (IOBuffer*)_ioBuf; // *** Temporary hack, _ioBuf is IOBuffer* but don't want to include XIO.hpp. + static const size_t ifdBufferSize = 12*65536; // Enough for the largest possible IFD. + std::vector<XMP_Uns8> ifdBuffer(ifdBufferSize); + XMP_Uns8 intBuffer [4]; // For the IFD count and offset to next IFD. + 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. + fileRef->Seek ( ifdOffset, kXMP_SeekFromStart ); + if ( ! XIO::CheckFileSpace ( fileRef, 2 ) ) return 0; // Bail for a truncated file. + fileRef->ReadAll ( intBuffer, 2 ); - bool ok = CheckFileSpace ( fileRef, ioBuf, 2 ); - if ( ! ok ) XMP_Throw ( "IFD count missing", kXMPErr_BadTIFF ); - XMP_Uns16 tagCount = this->GetUns16 ( ioBuf->ptr ); + XMP_Uns16 tagCount = this->GetUns16 ( intBuffer ); + if ( tagCount >= 0x8000 ) return 0; // Maybe wrong byte order. + if ( ! XIO::CheckFileSpace ( fileRef, 12*tagCount ) ) return 0; // Bail for a truncated file. + fileRef->ReadAll ( &ifdBuffer[0], 12*tagCount ); - 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 ); + if ( ! XIO::CheckFileSpace ( fileRef, 4 ) ) { + ifdInfo.origNextIFD = 0; // Tolerate a trncated file, do the remaining processing. + } else { + fileRef->ReadAll ( intBuffer, 4 ); + ifdInfo.origNextIFD = this->GetUns32 ( intBuffer ); } ifdInfo.origIFDOffset = ifdOffset; @@ -822,13 +874,11 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, X // 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. + XMP_Uns8* ifdPtr = &ifdBuffer[0]; // Move to the first IFD entry. - for ( XMP_Uns16 i = 0; i < tagCount; ++i, ioBuf->ptr += 12 ) { + for ( XMP_Uns16 i = 0; i < tagCount; ++i, ifdPtr += 12 ) { - if ( ! CheckFileSpace ( fileRef, ioBuf, 12 ) ) XMP_Throw ( "EOF within IFD", kXMPErr_BadTIFF ); - - RawIFDEntry* rawTag = (RawIFDEntry*)ioBuf->ptr; + RawIFDEntry* rawTag = (RawIFDEntry*)ifdPtr; XMP_Uns16 tagType = this->GetUns16 ( &rawTag->type ); if ( (tagType < kTIFF_ByteType) || (tagType > kTIFF_LastType) ) continue; // Bad type, skip this tag. @@ -840,7 +890,7 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, X 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. + mapTag.smallValue = GetUns32AsIs ( &rawTag->dataOrOffset ); // Keep the value or offset in stream byte ordering. if ( mapTag.dataLen <= 4 ) { mapTag.dataPtr = (XMP_Uns8*) &mapTag.smallValue; @@ -861,25 +911,14 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, X } - 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 ); + // ------------------------------------------------------------------------ + // Go back over the tag map and extract the data for large recognized tags. 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; @@ -889,46 +928,10 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, X 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. - + fileRef->Seek ( currTag->origDataOffset, kXMP_SeekFromStart ); 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. - } + fileRef->ReadAll ( currTag->dataPtr, currTag->dataLen ); } @@ -1262,6 +1265,9 @@ XMP_Uns32 TIFF_FileWriter::DetermineVisibleLength() #define Trace_DetermineAppendInfo 0 #endif +// An IFD grows if it has more tags than before. +#define DoesIFDGrow(ifd) (this->containedIFDs[ifd].origCount < this->containedIFDs[ifd].tagMap.size()) + XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, bool appendedIFDs[kTIFF_KnownIFDCount], XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount], @@ -1306,40 +1312,38 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, 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()); + appendedIFDs[kTIFF_InteropIFD] |= DoesIFDGrow ( kTIFF_InteropIFD ); 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()); + appendedIFDs[kTIFF_GPSInfoIFD] |= DoesIFDGrow ( kTIFF_GPSInfoIFD ); 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()); + appendedIFDs[kTIFF_ExifIFD] |= DoesIFDGrow ( kTIFF_ExifIFD ); 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()); + appendedIFDs[kTIFF_PrimaryIFD] |= DoesIFDGrow ( kTIFF_PrimaryIFD ); // The appended data (if any) will be a sequence of an IFD followed by its large values. // Determine the new offsets for the appended IFDs and tag values, and the total amount of - // appended stuff. + // appended stuff. The final IFD offset is set in newIFDOffsets for all IFDs, changed or not. + // This makes it easier to set the offsets to the primary and thumbnail IFDs when writing. for ( int ifd = 0; ifd < kTIFF_KnownIFDCount ;++ifd ) { InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); size_t tagCount = ifdInfo.tagMap.size(); + newIFDOffsets[ifd] = ifdInfo.origIFDOffset; // Make the new offset valid for unchanged IFDs. + 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) ); @@ -1488,7 +1492,7 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n this->PutUns32 ( currTag.count, ifdPtr ); ifdPtr += 4; - *((XMP_Uns32*)ifdPtr) = currTag.smallValue; + PutUns32AsIs ( currTag.smallValue, ifdPtr ); if ( (appendAll | currTag.changed) && (currTag.dataLen > 4) ) { @@ -1525,6 +1529,14 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n this->PutUns32 ( newIFDOffsets[kTIFF_PrimaryIFD], (newStream + 4) ); } + if ( appendedIFDs[kTIFF_TNailIFD] ) { + size_t primaryTagCount = this->containedIFDs[kTIFF_PrimaryIFD].tagMap.size(); + if ( primaryTagCount > 0 ) { + XMP_Uns32 tnailLinkOffset = newIFDOffsets[kTIFF_PrimaryIFD] + 2 + (12 * primaryTagCount); + this->PutUns32 ( newIFDOffsets[kTIFF_TNailIFD], (newStream + tnailLinkOffset) ); + } + } + } catch ( ... ) { free ( newStream ); @@ -1554,7 +1566,11 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n // 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: +// 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. +// +// These other 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 @@ -1563,11 +1579,11 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n // 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. +// Some of these will handled and kept, some will be thrown out, some will cause the rewrite to fail. +// At this time only the JPEG thumbnail tags, 513 and 514, contain hidden data that is kept. The +// final stream layout is whatever UpdateMemByAppend does for the visible content, followed by the +// hidden offset data. The Exif, GPS, and Interoperability IFDs are visible to UpdateMemByAppend. // ! 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) @@ -1653,7 +1669,7 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* } // Determine the offsets and additional size for the hidden offset content. Set the offset tags - // to the new offset. + // to the new offset so that UpdateMemByAppend writes the new offsets. XMP_Uns32 hiddenContentLength = 0; XMP_Uns32 hiddenContentOrigin = this->DetermineVisibleLength(); @@ -1678,6 +1694,7 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* // Save any old memory stream for the content behind hidden offsets. Setup a bare TIFF header. XMP_Uns8* oldStream = this->memStream; + bool ownedOldStream = this->ownedStream; XMP_Uns8 bareTIFF [8]; if ( this->bigEndian ) { @@ -1709,6 +1726,10 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* memcpy ( destPtr, srcPtr, hiddenLocations[i].length ); // AUDIT: Safe copy, not user data, computed length. } + + // Delete the old stream if appropriate. + + if ( ownedOldStream ) delete ( oldStream ); } // TIFF_FileWriter::UpdateMemByRewrite @@ -1730,6 +1751,7 @@ XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStr { if ( this->fileParsed ) XMP_Throw ( "Not memory based", kXMPErr_EnforceFailure ); + this->changed |= condenseStream; // Make sure a compaction request takes effect. if ( ! this->changed ) { if ( dataPtr != 0 ) *dataPtr = this->memStream; return this->tiffLength; @@ -1758,8 +1780,6 @@ XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStr 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 { @@ -1805,7 +1825,7 @@ XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStr #define Trace_UpdateFileStream 0 #endif -void TIFF_FileWriter::UpdateFileStream ( XMP_IO* fileRef ) +void TIFF_FileWriter::UpdateFileStream ( XMP_IO* fileRef, XMP_ProgressTracker* progressTracker ) { if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure ); if ( ! this->changed ) return; @@ -1832,6 +1852,33 @@ void TIFF_FileWriter::UpdateFileStream ( XMP_IO* fileRef ) XMP_Uns32 appendedLength = DetermineAppendInfo ( appendedOrigin, appendedIFDs, newIFDOffsets ); if ( appendedLength > (0xFFFFFFFFUL - appendedOrigin) ) XMP_Throw ( "TIFF files can't exceed 4GB", kXMPErr_BadTIFF ); + if ( progressTracker != 0 ) { + + float filesize=0; + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; + if ( ! thisIFD.changed ) continue; + + filesize += (thisIFD.tagMap.size() * sizeof(RawIFDEntry) + 6); + + 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) ) continue; + filesize += (thisTag.dataLen) ; + } + } + + if ( appendedIFDs[kTIFF_PrimaryIFD] ) filesize += 4; + XMP_Assert ( progressTracker->WorkInProgress() ); + progressTracker->AddTotalWork ( filesize ); + + } + // 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. diff --git a/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp b/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp index 3e96620..05cd87f 100644 --- a/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp +++ b/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp @@ -12,6 +12,7 @@ #include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" #include "source/XIO.hpp" +#include "source/EndianUtils.hpp" // ================================================================================================= /// \file TIFF_MemoryReader.cpp @@ -55,11 +56,11 @@ void TIFF_MemoryReader::SortIFD ( TweakedIFDInfo* thisIFD ) XMP_Uns16 tagCount = thisIFD->count; TweakedIFDEntry* ifdEntries = thisIFD->entries; - XMP_Uns16 prevTag = ifdEntries[0].id; + XMP_Uns16 prevTag = GetUns16AsIs ( &ifdEntries[0].id ); for ( size_t i = 1; i < tagCount; ++i ) { - XMP_Uns16 thisTag = ifdEntries[i].id; + XMP_Uns16 thisTag = GetUns16AsIs ( &ifdEntries[i].id ); if ( thisTag > prevTag ) { @@ -78,7 +79,7 @@ void TIFF_MemoryReader::SortIFD ( TweakedIFDInfo* thisIFD ) // 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 ( GetUns16AsIs(&ifdEntries[j].id) <= thisTag ) break; } if ( (j >= 0) && (ifdEntries[j].id == thisTag) ) { @@ -92,10 +93,20 @@ void TIFF_MemoryReader::SortIFD ( TweakedIFDInfo* thisIFD ) } 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; + #if ! SUNOS_SPARC + 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; + #else + void * tempifdEntries = &ifdEntries[i]; + TweakedIFDEntry temp; + memcpy ( &temp, tempifdEntries, sizeof(TweakedIFDEntry) ); + ++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. + tempifdEntries = &ifdEntries[j]; + memcpy ( tempifdEntries, &temp, sizeof(TweakedIFDEntry) ); + #endif } @@ -126,7 +137,7 @@ bool TIFF_MemoryReader::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const 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 ); + TagInfo info ( thisTag->id, thisTag->type, 0, 0, GetUns32AsIs(&thisTag->bytes) ); info.count = info.dataLen / (XMP_Uns32)kTIFF_TypeSizes[info.type]; info.dataPtr = this->GetDataPtr ( thisTag ); @@ -166,10 +177,11 @@ const TIFF_MemoryReader::TweakedIFDEntry* TIFF_MemoryReader::FindTagInIFD ( XMP_ // 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 ) { + XMP_Uns16 middleID = GetUns16AsIs ( &spanMiddle->id ); + if ( middleID == id ) { spanBegin = spanMiddle; break; - } else if ( spanMiddle->id > id ) { + } else if ( middleID > id ) { spanLength = halfLength; // Discard the middle. } else { spanBegin = spanMiddle; // Keep a valid spanBegin for the return check, don't use spanMiddle+1. @@ -178,7 +190,7 @@ const TIFF_MemoryReader::TweakedIFDEntry* TIFF_MemoryReader::FindTagInIFD ( XMP_ } - if ( spanBegin->id != id ) spanBegin = 0; + if ( GetUns16AsIs(&spanBegin->id) != id ) spanBegin = 0; return spanBegin; } // TIFF_MemoryReader::FindTagInIFD @@ -206,14 +218,18 @@ bool TIFF_MemoryReader::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) con { const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; - if ( (thisTag->type < kTIFF_ByteType) || (thisTag->type > kTIFF_LastType) ) return false; // Bad type, skip this tag. + + XMP_Uns16 thisType = GetUns16AsIs ( &thisTag->type ); + XMP_Uns32 thisBytes = GetUns32AsIs ( &thisTag->bytes ); + + if ( (thisType < kTIFF_ByteType) || (thisType > 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->id = GetUns16AsIs ( &thisTag->id ); + info->type = thisType; + info->count = thisBytes / (XMP_Uns32)kTIFF_TypeSizes[thisType]; + info->dataLen = thisBytes; info->dataPtr = this->GetDataPtr ( thisTag ); @@ -242,7 +258,7 @@ bool TIFF_MemoryReader::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* 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. + if ( GetUns32AsIs(&thisTag->bytes) != kTIFF_TypeSizes[thisTag->type] ) return false; // Wrong count. XMP_Uns32 uns32; XMP_Int32 int32; @@ -553,27 +569,40 @@ void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, 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 ); + if ( tnailIFDOffset != 0 ) { + if ( IsOffsetValid(tnailIFDOffset, 8, ifdLimit ) ) { // Ignore a bad Thumbnail IFD offset. + (void) this->ProcessOneIFD ( tnailIFDOffset, kTIFF_TNailIFD ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); + } + } const TweakedIFDEntry* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); - if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->bytes == 4) ) { + if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (GetUns32AsIs(&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) ) { + if ( (gpsIFDTag != 0) && (gpsIFDTag->type == kTIFF_LongType) && (GetUns32AsIs(&gpsIFDTag->bytes) == 4) ) { XMP_Uns32 gpsOffset = this->GetUns32 ( &gpsIFDTag->dataOrPos ); - if ( (8 <= gpsOffset) && (gpsOffset < ifdLimit) ) { // Ignore a bad GPS IFD offset. + if ( IsOffsetValid ( gpsOffset, 8, ifdLimit ) ) { // Ignore a bad GPS IFD offset. (void) this->ProcessOneIFD ( gpsOffset, kTIFF_GPSInfoIFD ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); } } const TweakedIFDEntry* interopIFDTag = this->FindTagInIFD ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); - if ( (interopIFDTag != 0) && (interopIFDTag->type == kTIFF_LongType) && (interopIFDTag->bytes == 4) ) { + if ( (interopIFDTag != 0) && (interopIFDTag->type == kTIFF_LongType) && (GetUns32AsIs(&interopIFDTag->bytes) == 4) ) { XMP_Uns32 interopOffset = this->GetUns32 ( &interopIFDTag->dataOrPos ); - if ( (8 <= interopOffset) && (interopOffset < ifdLimit) ) { // Ignore a bad Interoperability IFD offset. + if ( IsOffsetValid ( interopOffset, 8, ifdLimit ) ) { // Ignore a bad Interoperability IFD offset. (void) this->ProcessOneIFD ( interopOffset, kTIFF_InteropIFD ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); } } @@ -588,16 +617,22 @@ 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_Error error(kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); } 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 ( ifdCount >= 0x8000 ) { + XMP_Error error(kXMPErr_BadTIFF, "Outrageous IFD count" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); + } + if ( (XMP_Uns32)(2 + ifdCount*12 + 4) > (this->tiffLength - ifdOffset) ) { - XMP_Throw ( "Out of bounds IFD", kXMPErr_BadTIFF ); + XMP_Error error(kXMPErr_BadTIFF, "Out of bounds IFD" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); } ifdInfo.count = ifdCount; @@ -615,21 +650,48 @@ XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) Flip4 ( &thisEntry->bytes ); } - if ( thisEntry->id <= prevTag ) needsSorting = true; - prevTag = thisEntry->id; + if ( GetUns16AsIs(&thisEntry->id) <= prevTag ) needsSorting = true; + prevTag = GetUns16AsIs ( &thisEntry->id ); - if ( (thisEntry->type < kTIFF_ByteType) || (thisEntry->type > kTIFF_LastType) ) continue; // Bad type, skip this tag. + if ( (GetUns16AsIs(&thisEntry->type) < kTIFF_ByteType) || (GetUns16AsIs(&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 ! SUNOS_SPARC + + 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. + } } - if ( thisEntry->bytes > (this->tiffLength - thisEntry->dataOrPos) ) { - thisEntry->bytes = thisEntry->dataOrPos = 0; // Make this bad tag look empty. + + #else + + void *tempEntryByte = &thisEntry->bytes; + XMP_Uns32 temp = GetUns32AsIs(&thisEntry->bytes); + temp = temp * (XMP_Uns32)kTIFF_TypeSizes[GetUns16AsIs(&thisEntry->type)]; + memcpy ( tempEntryByte, &temp, sizeof(thisEntry->bytes) ); + + // thisEntry->bytes *= (XMP_Uns32)kTIFF_TypeSizes[thisEntry->type]; + if ( GetUns32AsIs(&thisEntry->bytes) > 4 ) { + void *tempEntryDataOrPos = &thisEntry->dataOrPos; + if ( ! this->nativeEndian ) Flip4 ( &thisEntry->dataOrPos ); + if ( (GetUns32AsIs(&thisEntry->dataOrPos) < 8) || (GetUns32AsIs(&thisEntry->dataOrPos) >= this->tiffLength) ) { + // thisEntry->bytes = thisEntry->dataOrPos = 0; // Make this bad tag look empty. + memset ( tempEntryByte, 0, sizeof(XMP_Uns32) ); + memset ( tempEntryDataOrPos, 0, sizeof(XMP_Uns32) ); + } + if ( GetUns32AsIs(&thisEntry->bytes) > (this->tiffLength - GetUns32AsIs(&thisEntry->dataOrPos)) ) { + // thisEntry->bytes = thisEntry->dataOrPos = 0; // Make this bad tag look empty. + memset ( tempEntryByte, 0, sizeof(XMP_Uns32) ); + memset ( tempEntryDataOrPos, 0, sizeof(XMP_Uns32) ); + } } - } + + #endif } diff --git a/XMPFiles/source/FormatSupport/TIFF_Support.cpp b/XMPFiles/source/FormatSupport/TIFF_Support.cpp index 9da9b52..da2f9b6 100644 --- a/XMPFiles/source/FormatSupport/TIFF_Support.cpp +++ b/XMPFiles/source/FormatSupport/TIFF_Support.cpp @@ -29,7 +29,7 @@ static bool sFirstCTor = true; TIFF_Manager::TIFF_Manager() - : bigEndian(false), nativeEndian(false), + : bigEndian(false), nativeEndian(false), errorCallbackPtr( NULL ), GetUns16(0), GetUns32(0), GetFloat(0), GetDouble(0), PutUns16(0), PutUns32(0), PutFloat(0), PutDouble(0) { @@ -379,6 +379,13 @@ static void UTF8_to_UTF16 ( const UTF8Unit * utf8Ptr, size_t utf8Len, bool bigEn } // UTF8_to_UTF16 +XMP_Bool IsOffsetValid( XMP_Uns32 offset, XMP_Uns32 lowerBound, XMP_Uns32 upperBound ) +{ + if ( (lowerBound <= offset) && (offset < upperBound) ) + return true; + return false; +} + // ================================================================================================= // TIFF_Manager::EncodeString // ========================== @@ -436,4 +443,14 @@ bool TIFF_Manager::EncodeString ( const std::string& utf8Str, XMP_Uns8 encoding, } // TIFF_Manager::EncodeString +void TIFF_Manager::NotifyClient( XMP_ErrorSeverity severity, XMP_Error & error ) +{ + if (this->errorCallbackPtr) + this->errorCallbackPtr->NotifyClient( severity, error ); + else { + if ( severity != kXMPErrSev_Recoverable ) + throw error; + } +} + // ================================================================================================= diff --git a/XMPFiles/source/FormatSupport/TIFF_Support.hpp b/XMPFiles/source/FormatSupport/TIFF_Support.hpp index 447ab79..002376b 100644 --- a/XMPFiles/source/FormatSupport/TIFF_Support.hpp +++ b/XMPFiles/source/FormatSupport/TIFF_Support.hpp @@ -23,6 +23,14 @@ #include "source/EndianUtils.hpp" +#include "source/Endian.h" + +#if SUNOS_SPARC + static const IEndian &IE = BigEndian::getInstance(); +#else + static const IEndian &IE = LittleEndian::getInstance(); +#endif //#if SUNOS_SPARC + // ================================================================================================= /// \file TIFF_Support.hpp /// \brief XMPFiles support for TIFF streams. @@ -642,7 +650,7 @@ public: 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; + virtual void UpdateFileStream ( XMP_IO* fileRef, XMP_ProgressTracker* progressTracker ) = 0; // --------------------------------------------------------------------------------------------- @@ -658,11 +666,17 @@ public: virtual ~TIFF_Manager() {}; + virtual void SetErrorCallback ( GenericErrorCallback * ec ) { this->errorCallbackPtr = ec; }; + + virtual void NotifyClient ( XMP_ErrorSeverity severity, XMP_Error & error ); + protected: bool bigEndian, nativeEndian; XMP_Uns32 CheckTIFFHeader ( const XMP_Uns8* tiffPtr, XMP_Uns32 length ); + // The pointer is to a buffer of the first 8 bytes. The length is the overall length, used + // to check the primary IFD offset. TIFF_Manager(); // Force clients to use the reader or writer derived classes. @@ -673,6 +687,8 @@ protected: XMP_Uns32 dataOrOffset; }; + GenericErrorCallback *errorCallbackPtr; + }; // TIFF_Manager @@ -730,7 +746,7 @@ public: 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(); }; + void UpdateFileStream ( XMP_IO* fileRef, XMP_ProgressTracker* progressTracker ) { NotAppropriate(); }; TIFF_MemoryReader() : ownedStream(false), tiffStream(0), tiffLength(0) {}; @@ -771,7 +787,12 @@ private: 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); }; + { if ( GetUns32AsIs(&tifdEntry->bytes) <= 4 ) { + return &tifdEntry->dataOrPos; + } else { + return (this->tiffStream + GetUns32AsIs(&tifdEntry->dataOrPos)); + } + } static inline void NotAppropriate() { XMP_Throw ( "Not appropriate for TIFF_Reader", kXMPErr_InternalFailure ); }; @@ -835,7 +856,7 @@ public: void IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ); XMP_Uns32 UpdateMemoryStream ( void** dataPtr, bool condenseStream = false ); - void UpdateFileStream ( XMP_IO* fileRef ); + void UpdateFileStream ( XMP_IO* fileRef, XMP_ProgressTracker* progressTracker ); TIFF_FileWriter(); @@ -934,8 +955,7 @@ private: 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. + XMP_Uns32 ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, XMP_IO* fileRef ); void ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XMP_Uns8 ifd ); @@ -958,6 +978,7 @@ private: }; // TIFF_FileWriter +XMP_Bool IsOffsetValid( XMP_Uns32 offset, XMP_Uns32 lowerBound, XMP_Uns32 upperBound ); // ================================================================================================= diff --git a/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp index a11e481..5b98ba0 100644 --- a/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp +++ b/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp @@ -26,7 +26,11 @@ static const XMP_Uns32 kSizeOriginationDate = 10; static const XMP_Uns32 kSizeOriginationTime = 8; // Needed to be able to memcpy directly to this struct. +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( 1 ) +#else #pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 struct BEXT { char mDescription[256]; @@ -40,7 +44,11 @@ static const XMP_Uns32 kSizeOriginationTime = 8; XMP_Uns8 mUMID[64]; XMP_Uns8 mReserved[190]; }; +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( ) +#else #pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 //----------------------------------------------------------------------------- // diff --git a/XMPFiles/source/FormatSupport/WAVE/CartMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/CartMetadata.cpp index 07c8969..73165e4 100644 --- a/XMPFiles/source/FormatSupport/WAVE/CartMetadata.cpp +++ b/XMPFiles/source/FormatSupport/WAVE/CartMetadata.cpp @@ -22,7 +22,11 @@ using namespace IFF_RIFF; // Types and globals for the stored form of the cart chunk. +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( 1 ) +#else #pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 struct StoredCartChunk { char Version[4]; // All of the fixed size text fields are null-filled local text, @@ -49,7 +53,11 @@ struct StoredCartChunk { static const size_t kMinimumCartChunkSize = sizeof(StoredCartChunk); +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( ) +#else #pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 static const size_t kFixedTextCount = (CartMetadata::kLastFixedTextField - CartMetadata::kFirstFixedTextField + 1); diff --git a/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.cpp index c9cbc23..277924f 100644 --- a/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.cpp +++ b/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.cpp @@ -24,7 +24,11 @@ static const XMP_Uns32 kSizeAppOtions = 16; static const XMP_Uns32 kSizeAppName = 32; // Needed to be able to memcpy directly to this struct. +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( 1 ) +#else #pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 struct Cr8rBoxContent { XMP_Uns32 mMagic; @@ -37,7 +41,11 @@ static const XMP_Uns32 kSizeAppName = 32; char mAppOptions[kSizeAppOtions]; char mAppName[kSizeAppName]; }; +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( ) +#else #pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 //----------------------------------------------------------------------------- // diff --git a/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.cpp index 76faaa3..6597f2a 100644 --- a/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.cpp +++ b/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.cpp @@ -217,6 +217,8 @@ XMP_Uns64 INFOMetadata::serialize( XMP_Uns8** outBuffer ) // memcpy( buffer+offset, &id, kSizeChunkID ); memcpy( buffer+offset+kSizeChunkID, &size, kSizeChunkSize ); + //size has been changed in little endian format. Change it back to bigendina + size = LE.getUns32( &size ); memcpy( buffer+offset+kChunkHeaderSize, value.c_str(), size ); // diff --git a/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.cpp index c5dc42e..477c31e 100644 --- a/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.cpp +++ b/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.cpp @@ -22,7 +22,11 @@ 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. +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( 1 ) +#else #pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 struct PrmlBoxContent { XMP_Uns32 mMagic; @@ -34,7 +38,11 @@ static const XMP_Uns32 kSizeFilePath = 260; XMP_Uns32 mMacParID; char mFilePath[kSizeFilePath]; }; +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( ) +#else #pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 //----------------------------------------------------------------------------- // diff --git a/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h b/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h index 1395cd1..b5a2e3f 100644 --- a/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h +++ b/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h @@ -33,7 +33,11 @@ class WAVEBehavior : public IChunkBehavior { // Internal structure to hold RF64 related data public: +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( 1 ) +#else #pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 struct ChunkSize64 { XMP_Uns64 size; @@ -55,7 +59,11 @@ public: // ctor DS64(): riffSize(0), dataSize(0), sampleCount(0), tableLength(0), trailingBytes(0) {} }; +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( ) +#else #pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 /** diff --git a/XMPFiles/source/FormatSupport/XDCAM_Support.cpp b/XMPFiles/source/FormatSupport/XDCAM_Support.cpp index b9704b9..cb65bf0 100644 --- a/XMPFiles/source/FormatSupport/XDCAM_Support.cpp +++ b/XMPFiles/source/FormatSupport/XDCAM_Support.cpp @@ -194,7 +194,7 @@ bool XDCAM_Support::GetLegacyMetadata ( SXMPMeta * xmpObjPtr, bool containsXMP = false; XML_NodePtr legacyContext = 0, legacyProp = 0; - XMP_StringPtr formatFPS; + XMP_StringPtr formatFPS = 0; // UMID if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DC, "identifier" )) ) { @@ -307,18 +307,20 @@ bool XDCAM_Support::GetLegacyMetadata ( SXMPMeta * xmpObjPtr, } } - // Frame rate + // Frame rate (always read, because its used later for the Duration + legacyProp = legacyContext->GetNamedElement ( legacyNS, "VideoFrame" ); + if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) { + formatFPS = legacyProp->GetAttrValue ( "formatFps" ); + } + + // only write back to XMP if framerate is not set in XMP yet 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; - } + 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" ); @@ -361,8 +363,12 @@ bool XDCAM_Support::GetLegacyMetadata ( SXMPMeta * xmpObjPtr, if ( durationValue != 0 ) durationFrames = durationValue; } - std::string timeScale = GetTimeScale ( formatFPS ); - + std::string timeScale; + if ( formatFPS ) { + + 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 ); diff --git a/XMPFiles/source/HandlerRegistry.cpp b/XMPFiles/source/HandlerRegistry.cpp index 144dbcf..abaa3a4 100644 --- a/XMPFiles/source/HandlerRegistry.cpp +++ b/XMPFiles/source/HandlerRegistry.cpp @@ -10,8 +10,13 @@ #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/HandlerRegistry.h" -#include "XMPFiles/source/PluginHandler/XMPAtoms.h" + +#if EnablePluginManager + #include "XMPFiles/source/PluginHandler/XMPAtoms.h" +#endif #if EnablePhotoHandlers #include "XMPFiles/source/FileHandlers/JPEG_Handler.hpp" @@ -47,7 +52,10 @@ //#endif using namespace Common; -using namespace XMP_PLUGIN; + +#if EnablePluginManager + using namespace XMP_PLUGIN; +#endif // ================================================================================================= @@ -374,7 +382,11 @@ XMP_FileFormat HandlerRegistry::getFileFormat( const std::string & fileExt, bool } } - return ResourceParser::getPluginFileFormat ( fileExt, addIfNotFound ); + #if EnablePluginManager + return ResourceParser::getPluginFileFormat ( fileExt, addIfNotFound ); + #else + return kXMP_UnknownFile; + #endif } // ================================================================================================= @@ -602,8 +614,7 @@ XMPFileHandlerInfo* HandlerRegistry::selectSmartHandler( XMPFiles* session, XMP_ { if( ( session->ioRef == 0 ) && (! ( handlerInfo->flags & kXMPFiles_HandlerOwnsFile ) ) ) { - session->ioRef = XMPFiles_IO::New_XMPFiles_IO( clientPath, readOnly ); - + session->ioRef = XMPFiles_IO::New_XMPFiles_IO( clientPath, readOnly, &session->errorCallback); if ( session->ioRef == 0 ) return 0; } @@ -731,7 +742,7 @@ XMPFileHandlerInfo* HandlerRegistry::selectSmartHandler( XMPFiles* session, XMP_ { if( (session->ioRef == 0) && (! (handlerInfo->flags & kXMPFiles_HandlerOwnsFile)) ) { - session->ioRef = XMPFiles_IO::New_XMPFiles_IO ( clientPath, readOnly ); + session->ioRef = XMPFiles_IO::New_XMPFiles_IO ( clientPath, readOnly, &session->errorCallback); if ( session->ioRef == 0 ) return 0; } else if( (session->ioRef != 0) && (handlerInfo->flags & kXMPFiles_HandlerOwnsFile) ) @@ -753,7 +764,7 @@ XMPFileHandlerInfo* HandlerRegistry::selectSmartHandler( XMPFiles* session, XMP_ if( session->ioRef == 0 ) { - session->ioRef = XMPFiles_IO::New_XMPFiles_IO ( clientPath, readOnly ); + session->ioRef = XMPFiles_IO::New_XMPFiles_IO ( clientPath, readOnly, &session->errorCallback ); if ( session->ioRef == 0 ) return 0; } diff --git a/XMPFiles/source/PluginHandler/FileHandler.h b/XMPFiles/source/PluginHandler/FileHandler.h index 16e4c6b..8d68258 100644 --- a/XMPFiles/source/PluginHandler/FileHandler.h +++ b/XMPFiles/source/PluginHandler/FileHandler.h @@ -51,12 +51,12 @@ 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) {} + mVersion(0), mUID(uid), mHandlerFlags(handlerFlags), mOverwrite(false), mType(type), mModule(module) {} virtual ~FileHandler(){} - inline XMP_Int64 getVersion() const { return mVersion; } - inline void setVersion( XMP_Uns32 version ) { mVersion = version; } + inline double getVersion() const { return mVersion; } + inline void setVersion( double version ) { mVersion = version; } inline const std::string& getUID() const { return mUID; } inline XMP_OptionBits getHandlerFlags() const { return mHandlerFlags; } @@ -88,7 +88,7 @@ private: typedef std::vector<CheckFormat> CheckFormatVec; CheckFormatVec mCheckFormatVec; - XMP_Uns32 mVersion; + double mVersion; std::string mUID; XMP_OptionBits mHandlerFlags; XMP_OptionBits mSerializeOption; diff --git a/XMPFiles/source/PluginHandler/FileHandlerInstance.cpp b/XMPFiles/source/PluginHandler/FileHandlerInstance.cpp index 42de1a4..3d2ffd8 100644 --- a/XMPFiles/source/PluginHandler/FileHandlerInstance.cpp +++ b/XMPFiles/source/PluginHandler/FileHandlerInstance.cpp @@ -30,12 +30,12 @@ FileHandlerInstance::~FileHandlerInstance() bool FileHandlerInstance::GetFileModDate ( XMP_DateTime * modDate ) { - bool ok; + XMP_Bool ok; WXMP_Error error; GetSessionFileModDateProc wGetFileModDate = mHandler->getModule()->getPluginAPIs()->mGetFileModDateProc; wGetFileModDate ( this->mObject, &ok, modDate, &error ); CheckError ( error ); - return ok; + return ConvertXMP_BoolToBool( ok ); } void FileHandlerInstance::CacheFileData() @@ -69,8 +69,26 @@ void FileHandlerInstance::ProcessXMP() 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 ); + if( mHandler->getModule()->getPluginAPIs()->mImportToXMPStringProc ) + { + std::string xmp; + this->xmpObj.SerializeToBuffer(&xmp, kXMP_NoOptions, 0); + XMP_StringPtr xmpStr=xmp.c_str(); + mHandler->getModule()->getPluginAPIs()->mImportToXMPStringProc( this->mObject, &xmpStr, &error ); + if( xmpStr!= NULL && xmpStr != xmp.c_str() ) + { + xmp.resize(0); + xmp.assign(xmpStr); + SXMPMeta newMeta(xmp.c_str(),xmp.length()); + this->xmpObj=newMeta; + free( (void*) xmpStr ); // It should be freed as documentation of mImportToXMPStringProc says so. + } + } + else + { + if( mHandler->getModule()->getPluginAPIs()->mImportToXMPProc ) + mHandler->getModule()->getPluginAPIs()->mImportToXMPProc( this->mObject, this->xmpObj.GetInternalRef(), &error ); + } CheckError( error ); } @@ -79,8 +97,18 @@ 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 ); + if( mHandler->getModule()->getPluginAPIs()->mExportFromXMPStringProc ) + { + std::string xmp; + this->xmpObj.SerializeToBuffer(&xmp, kXMP_NoOptions, 0); + XMP_StringPtr xmpStr=xmp.c_str(); + mHandler->getModule()->getPluginAPIs()->mExportFromXMPStringProc( this->mObject, xmpStr, &error ); + } + else + { + if( mHandler->getModule()->getPluginAPIs()->mExportFromXMPProc ) + mHandler->getModule()->getPluginAPIs()->mExportFromXMPProc( this->mObject, this->xmpObj.GetInternalRef(), &error ); + } CheckError( error ); this->xmpObj.SerializeToBuffer ( &this->xmpPacket, mHandler->getSerializeOption() ); @@ -103,4 +131,53 @@ void FileHandlerInstance::WriteTempFile( XMP_IO* tempRef ) CheckError( error ); } +static void SetStringVector ( StringVectorRef clientPtr, XMP_StringPtr * arrayPtr, XMP_Uns32 stringCount ) +{ + std::vector<std::string>* clientVec = (std::vector<std::string>*) clientPtr; + clientVec->clear(); + for ( XMP_Uns32 i = 0; i < stringCount; ++i ) { + std::string nextValue ( arrayPtr[i] ); + clientVec->push_back ( nextValue ); + } +} + + +void FileHandlerInstance::FillMetadataFiles( std::vector<std::string> * metadataFiles ) +{ + WXMP_Error error; + FillMetadataFilesProc wFillMetadataFilesProc = mHandler->getModule()->getPluginAPIs()->mFillMetadataFilesProc; + if ( wFillMetadataFilesProc ) { + wFillMetadataFilesProc( this->mObject, metadataFiles, SetStringVector, &error); + CheckError( error ); + } else { + XMP_Throw ( "This version of plugin does not support FillMetadataFiles API", kXMPErr_Unimplemented ); + } +} + +void FileHandlerInstance::FillAssociatedResources( std::vector<std::string> * resourceList ) +{ + WXMP_Error error; + FillAssociatedResourcesProc wFillAssociatedResourcesProc = mHandler->getModule()->getPluginAPIs()->mFillAssociatedResourcesProc; + if ( wFillAssociatedResourcesProc ) { + wFillAssociatedResourcesProc( this->mObject, resourceList, SetStringVector, &error); + CheckError( error ); + } else { + XMP_Throw ( "This version of plugin does not support FillAssociatedResources API", kXMPErr_Unimplemented ); + } +} + +bool FileHandlerInstance::IsMetadataWritable( ) +{ + WXMP_Error error; + XMP_Bool result = kXMP_Bool_False; + IsMetadataWritableProc wIsMetadataWritableProc = mHandler->getModule()->getPluginAPIs()->mIsMetadataWritableProc; + if ( wIsMetadataWritableProc ) { + wIsMetadataWritableProc( this->mObject, &result, &error); + CheckError( error ); + } else { + XMP_Throw ( "This version of plugin does not support IsMetadataWritable API", kXMPErr_Unimplemented ); + } + return ConvertXMP_BoolToBool( result ); +} + } //namespace XMP_PLUGIN diff --git a/XMPFiles/source/PluginHandler/FileHandlerInstance.h b/XMPFiles/source/PluginHandler/FileHandlerInstance.h index 5cf2420..9810831 100644 --- a/XMPFiles/source/PluginHandler/FileHandlerInstance.h +++ b/XMPFiles/source/PluginHandler/FileHandlerInstance.h @@ -34,6 +34,9 @@ public: //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 ); + virtual void FillMetadataFiles ( std::vector<std::string> * metadataFiles ); + virtual void FillAssociatedResources ( std::vector<std::string> * resourceList ); + virtual bool IsMetadataWritable ( ); inline SessionRef GetSession() const { return mObject; } inline FileHandlerSharedPtr GetHandlerInfo() const { return mHandler; } diff --git a/XMPFiles/source/PluginHandler/HostAPIImpl.cpp b/XMPFiles/source/PluginHandler/HostAPIImpl.cpp index 04dcdd0..cf6a389 100644 --- a/XMPFiles/source/PluginHandler/HostAPIImpl.cpp +++ b/XMPFiles/source/PluginHandler/HostAPIImpl.cpp @@ -49,7 +49,7 @@ static void HandleException( WXMP_Error* wError ) // FileIO_API // -static XMPErrorID FileSysRead( XMP_IORef io, void* buffer, XMP_Uns32 count, bool readAll, XMP_Uns32& byteRead, WXMP_Error * wError ) +static XMPErrorID FileSysRead( XMP_IORef io, void* buffer, XMP_Uns32 count, XMP_Bool readAll, XMP_Uns32& byteRead, WXMP_Error * wError ) { if( wError == NULL ) return kXMPErr_BadParam; @@ -60,7 +60,7 @@ static XMPErrorID FileSysRead( XMP_IORef io, void* buffer, XMP_Uns32 count, bool if( io != NULL ) { ::XMP_IO * thiz = (::XMP_IO*)io; - byteRead = thiz->Read( buffer, count, readAll ); + byteRead = thiz->Read( buffer, count, ConvertXMP_BoolToBool( readAll ) ); wError->mErrorID = kXMPErr_NoError; } } @@ -318,7 +318,7 @@ static void GetStringAPI( String_API* strAPI ) // Abort_API // -static XMPErrorID CheckAbort( SessionRef session, bool* aborted, WXMP_Error* wError ) +static XMPErrorID CheckAbort( SessionRef session, XMP_Bool * aborted, WXMP_Error* wError ) { if( wError == NULL ) return kXMPErr_BadParam; @@ -326,7 +326,7 @@ static XMPErrorID CheckAbort( SessionRef session, bool* aborted, WXMP_Error* wEr if( aborted ) { - *aborted = false; + *aborted = kXMP_Bool_False; // // find FileHandlerInstance associated to session reference @@ -346,7 +346,7 @@ static XMPErrorID CheckAbort( SessionRef session, bool* aborted, WXMP_Error* wEr { try { - *aborted = proc( arg ); + *aborted = ConvertBoolToXMP_Bool( proc( arg ) ); } catch( ... ) { @@ -376,7 +376,7 @@ static void GetAbortAPI( Abort_API* abortAPI ) // StandardHandler_API // -static XMPErrorID CheckFormatStandardHandler( SessionRef session, XMP_FileFormat format, StringPtr path, bool& checkOK, WXMP_Error* wError ) +static XMPErrorID CheckFormatStandardHandler( SessionRef session, XMP_FileFormat format, StringPtr path, XMP_Bool & checkOK, WXMP_Error* wError ) { if( wError == NULL ) return kXMPErr_BadParam; @@ -407,7 +407,7 @@ static XMPErrorID CheckFormatStandardHandler( SessionRef session, XMP_FileFormat // XMPFiles standardClient; standardClient.format = format; - standardClient.filePath = std::string( path ); + standardClient.SetFilePath( path ); if( hdlInfo->flags & kXMPFiles_FolderBasedFormat ) { @@ -508,7 +508,7 @@ static XMPErrorID CheckFormatStandardHandler( SessionRef session, XMP_FileFormat return wError->mErrorID; } -static XMPErrorID GetXMPStandardHandler( SessionRef session, XMP_FileFormat format, StringPtr path, XMPMetaRef xmpRef, bool* xmpExists, WXMP_Error* wError ) +static XMPErrorID GetXMPStandardHandler( SessionRef session, XMP_FileFormat format, StringPtr path, XMPMetaRef xmpRef, XMP_Bool * xmpExists, WXMP_Error* wError ) { if( wError == NULL ) return kXMPErr_BadParam; @@ -532,18 +532,18 @@ static XMPErrorID GetXMPStandardHandler( SessionRef session, XMP_FileFormat form // // check format first // - bool suc = false; + XMP_Bool suc = kXMP_Bool_False; XMPErrorID errorID = CheckFormatStandardHandler( session, format, path, suc, wError ); - if( errorID == kXMPErr_NoError && suc ) + if( errorID == kXMPErr_NoError && ConvertXMP_BoolToBool( suc ) ) { // // setup temporary XMPFiles instance // XMPFiles standardClient; standardClient.format = format; - standardClient.filePath = std::string( path ); + standardClient.SetFilePath( path ); SXMPMeta meta( xmpRef ); @@ -601,6 +601,27 @@ static XMPErrorID GetXMPStandardHandler( SessionRef session, XMP_FileFormat form return wError->mErrorID; } +static XMPErrorID GetXMPStandardHandler_V2( SessionRef session, XMP_FileFormat format, StringPtr path, XMP_StringPtr* xmpStr, XMP_Bool * xmpExists, WXMP_Error* wError ) +{ + SXMPMeta meta; + std::string xmp; + GetXMPStandardHandler( session, format, path, meta.GetInternalRef(), xmpExists, wError ) ; + if( wError->mErrorID != kXMPErr_NoError ) + return wError->mErrorID; + + meta.SerializeToBuffer(&xmp, kXMP_NoOptions, 0); + XMP_Uns32 length = (XMP_Uns32)xmp.size() + 1 ; + StringPtr buffer = NULL; + CreateBuffer( &buffer,length ,wError); + if( wError->mErrorID != kXMPErr_NoError ) + return wError->mErrorID; + + memcpy( buffer, xmp.c_str(), length ); + *xmpStr = buffer; // callee function should free the memory. + return wError->mErrorID ; +} + + static void GetStandardHandlerAPI( StandardHandler_API* standardHandlerAPI ) { if( standardHandlerAPI ) @@ -608,13 +629,60 @@ static void GetStandardHandlerAPI( StandardHandler_API* standardHandlerAPI ) standardHandlerAPI->mCheckFormatStandardHandler = CheckFormatStandardHandler; standardHandlerAPI->mGetXMPStandardHandler = GetXMPStandardHandler; } + +} + + +static XMPErrorID RequestAPISuite( const char* apiName, XMP_Uns32 apiVersion, void** apiSuite, WXMP_Error* wError ) +{ + if( wError == NULL ) + { + return kXMPErr_BadParam; + } + + wError->mErrorID = kXMPErr_NoError; + + if( apiName == NULL + || apiVersion == 0 + || apiSuite == NULL ) + { + wError->mErrorID = kXMPErr_BadParam; + return kXMPErr_BadParam; + } + + // dummy suite used by unit test + if( strcmp( apiName, "testDummy" ) == 0 && apiVersion == 1 ) + { + *apiSuite = (void*) &RequestAPISuite; + } + else if ( ! strcmp( apiName, "StandardHandler" ) && apiVersion == 2 ) + { + static const StandardHandler_API_V2 standardHandlerAPI = + { + &CheckFormatStandardHandler, + &GetXMPStandardHandler_V2 + }; + *apiSuite=(void*)&standardHandlerAPI; + } + else + { + wError->mErrorID = kXMPErr_Unimplemented; + } + + return wError->mErrorID; } + /////////////////////////////////////////////////////////////////////////////// // // Init/Term Host APIs // +// Because of changes to the plugin versioning strategy, +// the host API version is no longer tied to the plugin version +// and the host API struct is supposed to be frozen. +// New host APIs can be requested through the new function mRequestAPISuite. + void PluginManager::SetupHostAPI_V1( HostAPIRef hostAPI ) { // Get XMP_IO APIs @@ -632,6 +700,24 @@ void PluginManager::SetupHostAPI_V1( HostAPIRef hostAPI ) // Get standard handler APIs hostAPI->mStandardHandlerAPI = new StandardHandler_API(); GetStandardHandlerAPI( hostAPI->mStandardHandlerAPI ); + + hostAPI->mRequestAPISuite = NULL; +} + +void PluginManager::SetupHostAPI_V2( HostAPIRef hostAPI ) +{ + SetupHostAPI_V1 ( hostAPI ); +} + +void PluginManager::SetupHostAPI_V3( HostAPIRef hostAPI ) +{ + SetupHostAPI_V2 ( hostAPI ); +} + +void PluginManager::SetupHostAPI_V4( HostAPIRef hostAPI ) +{ + SetupHostAPI_V3 ( hostAPI ); + hostAPI->mRequestAPISuite = RequestAPISuite; } } //namespace XMP_PLUGIN diff --git a/XMPFiles/source/PluginHandler/Module.cpp b/XMPFiles/source/PluginHandler/Module.cpp index 91b8ea8..595c17d 100644 --- a/XMPFiles/source/PluginHandler/Module.cpp +++ b/XMPFiles/source/PluginHandler/Module.cpp @@ -8,28 +8,153 @@ // ================================================================================================= #include "Module.h" +#include "HostAPI.h" namespace XMP_PLUGIN { +static bool CheckAPICompatibility_V1 ( const PluginAPIRef pluginAPIs ) +{ + // these plugin APIs are mandatory to run an XMP file handler + if ( pluginAPIs->mTerminatePluginProc + && pluginAPIs->mSetHostAPIProc + && pluginAPIs->mInitializeSessionProc + && pluginAPIs->mTerminateSessionProc + && pluginAPIs->mCheckFileFormatProc + && pluginAPIs->mCheckFolderFormatProc + && pluginAPIs->mGetFileModDateProc + && pluginAPIs->mCacheFileDataProc + && pluginAPIs->mUpdateFileProc + && pluginAPIs->mWriteTempFileProc ) + { + return true; + } + return false; +} + +static bool CheckAPICompatibility_V2 ( const PluginAPIRef pluginAPIs ) +{ + if ( CheckAPICompatibility_V1 ( pluginAPIs ) ) + { + if ( pluginAPIs->mFillMetadataFilesProc + && pluginAPIs->mFillAssociatedResourcesProc ) + { + return true; + } + } + return false; +} + +static bool CheckAPICompatibility_V3 ( const PluginAPIRef pluginAPIs ) +{ + if ( CheckAPICompatibility_V2 ( pluginAPIs ) ) + { + if ( pluginAPIs->mIsMetadataWritableProc ) + { + return true; + } + } + return false; +} + +static bool CheckAPICompatibility ( const PluginAPIRef pluginAPIs ) +{ + // Note: This is the place where we can reject old plugins. + // For example if we consider all functionality of + // plugin API version 2 mandatory we can reject + // plugin version 1 by returning false in case 1. + switch ( pluginAPIs->mVersion ) + { + case 1: + return CheckAPICompatibility_V1 ( pluginAPIs ); + break; + + case 2: + return CheckAPICompatibility_V2 ( pluginAPIs ); + break; + + case 3: + return CheckAPICompatibility_V3 ( pluginAPIs ); + break; + + default: + // The loaded plugin is newer than the host. + // Only basic functionality to run the plugin is required. + return CheckAPICompatibility_V1 ( pluginAPIs ); + break; + } +} + PluginAPIRef Module::getPluginAPIs() { // // return ref. to Plugin API, load module if not yet loaded // - if( !mPluginAPIs && !load() ) + if ( !mPluginAPIs || mLoaded != kModuleLoaded ) { - XMP_Throw ( "Plugin API not available.", kXMPErr_Unavailable ); + if ( !load() ) + { + XMP_Throw ( "Plugin API not available.", kXMPErr_Unavailable ); + } } - return mPluginAPIs; } bool Module::load() { + XMP_AutoLock lock ( &mLoadingLock, kXMP_WriteLock ); + return loadInternal(); +} + +void Module::unload() +{ + XMP_AutoLock lock (&mLoadingLock, kXMP_WriteLock); + unloadInternal(); +} + +void Module::unloadInternal() +{ + 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 ); +} + +bool Module::loadInternal() +{ if( mLoaded == kModuleNotLoaded ) { - std::string errorMsg; + const char * errorMsg = NULL; // // load module @@ -43,63 +168,88 @@ bool Module::load() // get entry point function pointer // InitializePluginProc InitializePlugin = reinterpret_cast<InitializePluginProc>( - GetFunctionPointerFromModuleImpl(mHandle, "InitializePlugin") ); - - if( InitializePlugin != NULL ) + GetFunctionPointerFromModuleImpl(mHandle, "InitializePlugin") ); // legacy entry point + + InitializePlugin2Proc InitializePlugin2 = reinterpret_cast<InitializePlugin2Proc>( + GetFunctionPointerFromModuleImpl(mHandle, "InitializePlugin2") ); + + if( InitializePlugin2 != NULL || InitializePlugin != NULL ) { - // - // get module ID from plugin resource - // std::string moduleID; GetResourceDataFromModule(mHandle, "MODULE_IDENTIFIER", "txt", moduleID); mPluginAPIs = new PluginAPI(); - + memset( mPluginAPIs, 0, sizeof(PluginAPI) ); + mPluginAPIs->mSize = sizeof(PluginAPI); + mPluginAPIs->mVersion = XMP_PLUGIN_VERSION; // informational: the latest version that the host knows about + + WXMP_Error error; + // // 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 - ) + if( InitializePlugin2 != NULL ) { - // - // set host API at plugin - // - HostAPIRef hostAPI = PluginManager::getHostAPI( mPluginAPIs->mVersion ); - mPluginAPIs->mSetHostAPIProc( hostAPI, &error ); - - if( error.mErrorID == kXMPErr_NoError ) + HostAPIRef hostAPI = PluginManager::getHostAPI( XMP_HOST_API_VERSION ); + InitializePlugin2( moduleID.c_str(), hostAPI, mPluginAPIs, &error ); + + if ( error.mErrorID == kXMPErr_NoError ) { - mLoaded = kModuleLoaded; + // check all function pointers are correct based on version numbers + if( CheckAPICompatibility( mPluginAPIs ) ) + { + mLoaded = kModuleLoaded; + } + else + { + errorMsg = "Incompatible plugin API version."; + } } else { - errorMsg = "Incompatible plugin API version. (" + moduleID + ")"; + errorMsg = "Plugin initialization failed."; } } - else + else if( InitializePlugin != NULL ) { - if( error.mErrorID != kXMPErr_NoError ) - { - errorMsg = "Plugin initialization failed. (" + moduleID + ")"; + // initialize through legacy plugin entry point + InitializePlugin( moduleID.c_str(), mPluginAPIs, &error ); + + if ( error.mErrorID == kXMPErr_NoError ) { + // check all function pointers are correct based on version numbers + bool compatibleAPIs = CheckAPICompatibility(mPluginAPIs); + + if ( compatibleAPIs ) + { + // + // set host API at plugin + // + HostAPIRef hostAPI = PluginManager::getHostAPI( mPluginAPIs->mVersion ); + + mPluginAPIs->mSetHostAPIProc( hostAPI, &error ); + + if( error.mErrorID == kXMPErr_NoError ) + { + mLoaded = kModuleLoaded; + } + else + { + errorMsg = "Plugin API incomplete."; + } + } + else + { + errorMsg = "Incompatible plugin API version."; + } } else { - errorMsg = "Plugin API incomplete. (" + moduleID + ")"; + errorMsg = "Plugin initialization failed."; } } - } - else - { - errorMsg = "Missing plugin entry point \"InitializePlugin\" in plugin " + mPath; + else + { + errorMsg = "Missing plugin entry point in plugin"; + } } if( mLoaded != kModuleLoaded ) @@ -108,62 +258,25 @@ bool Module::load() // plugin wasn't loaded and initialized successfully, // so unload the module // - this->unload(); + this->unloadInternal(); } } else { - errorMsg = "Can't load module " + mPath; + errorMsg = "Can't load module"; } - if( mLoaded != kModuleLoaded ) + if ( mLoaded != kModuleLoaded && errorMsg ) { // // error occurred // - throw XMP_Error( kXMPErr_InternalFailure, errorMsg.c_str() ); + throw XMP_Error( kXMPErr_InternalFailure, errorMsg); } } 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 index 0e308a0..1d7fdd9 100644 --- a/XMPFiles/source/PluginHandler/Module.h +++ b/XMPFiles/source/PluginHandler/Module.h @@ -48,6 +48,17 @@ public: void unload(); private: + + /************************************************************************/ + /* Loads the module without acquiring the lock */ + /************************************************************************/ + bool loadInternal(); + + /************************************************************************/ + /* Unloads the module without acquiring the lock */ + /************************************************************************/ + void unloadInternal(); + typedef enum { kModuleNotLoaded = 0, @@ -59,6 +70,7 @@ private: OS_ModuleRef mHandle; PluginAPIRef mPluginAPIs; LoadStatus mLoaded; + XMP_ReadWriteLock mLoadingLock; }; } //namespace XMP_PLUGIN diff --git a/XMPFiles/source/PluginHandler/OS_Utils_Linux.cpp b/XMPFiles/source/PluginHandler/OS_Utils_Linux.cpp index 1fe1f9f..7808a98 100644 --- a/XMPFiles/source/PluginHandler/OS_Utils_Linux.cpp +++ b/XMPFiles/source/PluginHandler/OS_Utils_Linux.cpp @@ -26,6 +26,7 @@ static ModuleRefToPathMap sMapModuleRefToPath; //typedef std::map<void*, std::string> ResourceFileToPathMap; typedef std::map<OS_ModuleRef, std::string> ResourceFileToPathMap; static ResourceFileToPathMap sMapResourceFileToPath; +static XMP_ReadWriteLock sMapModuleRWLock; typedef std::tr1::shared_ptr<int> FilePtr; @@ -104,6 +105,7 @@ OS_ModuleRef LoadModule( const std::string & inModulePath, bool inOnlyResourceAc } else { // success ! + XMP_AutoLock writeLock ( &sMapModuleRWLock, kXMP_WriteLock ); ModuleRefToPathMap::const_iterator iter = sMapModuleRefToPath.find(result); if( iter == sMapModuleRefToPath.end() ) { @@ -136,6 +138,7 @@ void UnloadModule( OS_ModuleRef inModule, bool inOnlyResourceAccess ) } else { + XMP_AutoLock writeLock ( &sMapModuleRWLock, kXMP_WriteLock ); ModuleRefToPathMap::iterator iter = sMapModuleRefToPath.find(inModule); if( iter != sMapModuleRefToPath.end() ) { @@ -160,7 +163,12 @@ static std::string GetModulePath( if( inOSModule != NULL ) { - ModuleRefToPathMap::const_iterator iter = sMapModuleRefToPath.find(inOSModule); + ModuleRefToPathMap::const_iterator iter; + { + XMP_AutoLock readLock ( &sMapModuleRWLock, kXMP_ReadLock ); + iter = sMapModuleRefToPath.find(inOSModule); + } + ResourceFileToPathMap::const_iterator iter2 = sMapResourceFileToPath.find(inOSModule); if( (iter != sMapModuleRefToPath.end()) && (iter2 != sMapResourceFileToPath.end()) ) { diff --git a/XMPFiles/source/PluginHandler/OS_Utils_Mac.cpp b/XMPFiles/source/PluginHandler/OS_Utils_Mac.cpp index c042eb7..e9c2b46 100644 --- a/XMPFiles/source/PluginHandler/OS_Utils_Mac.cpp +++ b/XMPFiles/source/PluginHandler/OS_Utils_Mac.cpp @@ -166,9 +166,6 @@ private: typedef AutoCFRef<CFURLRef> AutoCFURL; typedef AutoCFRef<CFStringRef> AutoCFString; -typedef std::tr1::shared_ptr<FSIORefNum> FilePtr; - - /** ************************************************************************************************************************ ** Convert string into CFString */ @@ -178,85 +175,6 @@ static inline CFStringRef MakeCFString(const std::string& inString, CFStringEnco 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; @@ -303,12 +221,11 @@ OS_ModuleRef LoadModule( const std::string & inModulePath, bool inOnlyResourceAc 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; + throw XMP_Error( kXMPErr_InternalFailure, "Failed to load module" ); } } } @@ -341,26 +258,42 @@ bool GetResourceDataFromModule( std::string & outBuffer) { bool success = false; - - if (FilePtr file = OpenResourceFile(inOSModule, inResourceName, inResourceType, false)) + bool inSearchInSubFolderWithNameOfResourceType = false; + AutoCFString resourceName( MakeCFString( inResourceName ) ); + AutoCFString resourceType( MakeCFString( inResourceType ) ); + AutoCFString subfolderName( inSearchInSubFolderWithNameOfResourceType ? MakeCFString( inResourceType ) : nil ); + + AutoCFURL url( ::CFBundleCopyResourceURL( inOSModule, *resourceName, *resourceType, *subfolderName ) ); + + if ( !url.IsNull() ) { - 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() ) + typedef AutoCFRef<CFDataRef> AutoCFData; + typedef AutoCFRef<CFNumberRef> AutoCFNumber; + AutoCFData resourceData; + SInt32 errorCode = 0; + AutoCFNumber length; + *length = reinterpret_cast<CFNumberRef> ( ::CFURLCreatePropertyFromResource( kCFAllocatorDefault, *url, kCFURLFileLength, &errorCode ) ); + + if ( !errorCode ) { - size = static_cast<ByteCount>(fork_size); - if (size > 0) + SInt64 sizeOfFile = 0; + success = ::CFNumberGetValue( *length, kCFNumberSInt64Type, &sizeOfFile ); + // presumingly we don't want to load more than 2GByte at once (!) + if ( success && sizeOfFile < std::numeric_limits<XMP_Int32>::max() ) { - outBuffer.resize(size); - ::FSReadFork(*file.get(), fsFromStart, 0, size, (unsigned char*)&outBuffer[0], (ByteCount*)&size); + success = ::CFURLCreateDataAndPropertiesFromResource( kCFAllocatorDefault, *url, &(*resourceData), NULL, NULL, &errorCode ); + if ( success && errorCode == 0 ) + { + outBuffer.resize( sizeOfFile ); + CFRange range = CFRangeMake (0, sizeOfFile ); + ::CFDataGetBytes( *resourceData, range, reinterpret_cast< UInt8 * > (&outBuffer[0]) ); + return true; + } } - success = true; } } - return success; + return false; } } //namespace XMP_PLUGIN diff --git a/XMPFiles/source/PluginHandler/PluginManager.cpp b/XMPFiles/source/PluginHandler/PluginManager.cpp index af60388..3802c9b 100644 --- a/XMPFiles/source/PluginHandler/PluginManager.cpp +++ b/XMPFiles/source/PluginHandler/PluginManager.cpp @@ -57,7 +57,7 @@ static XMPFileHandler* Plugin_MetaHandlerCTor ( FileHandlerSharedPtr handler, XM 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 ); + handler->getModule()->getPluginAPIs()->mInitializeSessionProc ( handler->getUID().c_str(), parent->GetFilePath().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 ); @@ -87,12 +87,12 @@ static bool Plugin_CheckFileFormat ( FileHandlerSharedPtr handler, XMP_StringPtr // call into plugin if owning handler or if manifest has no CheckFormat entry if ( fileRef == 0 || handler->getCheckFormatSize() == 0) { - bool ok; + XMP_Bool ok; WXMP_Error error; CheckSessionFileFormatProc checkProc = handler->getModule()->getPluginAPIs()->mCheckFileFormatProc; checkProc ( handler->getUID().c_str(), filePath, fileRef, &ok, &error ); CheckError ( error ); - return ok; + return ConvertXMP_BoolToBool( ok ); } else { @@ -123,10 +123,11 @@ static bool Plugin_CheckFileFormat ( FileHandlerSharedPtr handler, XMP_StringPtr // Check if byteSeq is hexadecimal byte sequence, e.g 0x03045100 - bool isHex = ( (checkFormat.mLength > 2) && + bool isHex = ( (checkFormat.mLength > 0 ) && + (checkFormat.mByteSeq.size() == (2 + 2*checkFormat.mLength) && (checkFormat.mByteSeq[0] == '0') && - (checkFormat.mByteSeq[1] == 'x') && - (checkFormat.mByteSeq.size() == (2 + 2*checkFormat.mLength)) ); + (checkFormat.mByteSeq[1] == 'x') ) + ); if ( ! isHex ) { @@ -184,7 +185,7 @@ static bool Plugin_CheckFolderFormat( FileHandlerSharedPtr handler, const std::string & leafName, XMPFiles * parent ) { - bool result = false; + XMP_Bool result = false; if ( handler != 0 ) { @@ -194,7 +195,7 @@ static bool Plugin_CheckFolderFormat( FileHandlerSharedPtr handler, CheckError( error ); } - return result; + return ConvertXMP_BoolToBool( result ); } @@ -226,7 +227,8 @@ static bool Plugin_CheckFolderFormat_Replacement( XMP_FileFormat format, PluginManager* PluginManager::msPluginManager = 0; -PluginManager::PluginManager( const std::string& pluginDir, const std::string& plugins ) : mPluginDir ( pluginDir ) +PluginManager::PluginManager( const std::string& pluginDir, const std::string& plugins ) +: mPluginDir ( pluginDir ) { const std::size_t count = sizeof(kLibraryExtensions) / sizeof(kLibraryExtensions[0]); @@ -303,10 +305,9 @@ PluginManager::~PluginManager() mPluginDir.clear(); mExtensions.clear(); mPluginsNeeded.clear(); - mModules.clear(); mHandlers.clear(); mSessions.clear(); - + terminateHostAPI(); } @@ -322,6 +323,18 @@ static bool registerHandler( XMP_FileFormat format, FileHandlerSharedPtr handler CheckFolderFormatProc chkFolderFormat = NULL; XMPFileHandlerCTor hdlCtor = NULL; + if ( handler->getHandlerFlags() & kXMPFiles_NeedsPreloading ) + { + try + { + handler->load(); + } + catch ( ... ) + { + return false; + } + } + if( handler->getOverwriteHandler() ) { // @@ -374,6 +387,7 @@ void PluginManager::initialize( const std::string& pluginDir, const std::string& { HandlerRegistry & hdlrReg = HandlerRegistry::getInstance(); if( msPluginManager == 0 ) msPluginManager = new PluginManager( pluginDir, plugins ); + msPluginManager->initializeHostAPI(); msPluginManager->doScan( 2 ); @@ -430,16 +444,31 @@ void PluginManager::addFileHandler( XMP_FileFormat format, FileHandlerSharedPtr } // + // // if there is already a standard handler or a replacement handler for the file format - // then just ignore it. The first one wins. + // then use the one with the highest version. If both versions are the same the first one wins. // - if( handler->getOverwriteHandler() ) + FileHandlerSharedPtr& existingHandler = + handler->getOverwriteHandler() ? handlerMap[format].mReplacementHandler : handlerMap[format].mStandardHandler; + + if( ! existingHandler ) { - if( handlerMap[format].mReplacementHandler.get() == NULL ) handlerMap[format].mReplacementHandler = handler; + existingHandler = handler; } else { - if( handlerMap[format].mStandardHandler.get() == NULL ) handlerMap[format].mStandardHandler = handler; + if( existingHandler->getUID() == handler->getUID() ) + { + if( existingHandler->getVersion() < handler->getVersion() ) + { + existingHandler = handler; // replace older handler + } + } + else + { + // TODO: notify client that two plugin handlers try to handle the same file format + // -> need access to the global notification handler + } } } } @@ -469,13 +498,13 @@ FileHandlerSharedPtr PluginManager::getFileHandler( XMP_FileFormat format, Handl } // ================================================================================================= - -static XMP_ReadWriteLock sPluginManagerRWLock; + +static XMP_ReadWriteLock sSessionMapPluginManagerRWLock; void PluginManager::addHandlerInstance( SessionRef session, FileHandlerInstancePtr handler ) { if ( msPluginManager != 0 ) { - XMP_AutoLock(&sPluginManagerRWLock, kXMP_WriteLock); + XMP_AutoLock lock(&sSessionMapPluginManagerRWLock, kXMP_WriteLock); SessionMap & sessionMap = msPluginManager->mSessions; if ( sessionMap.find(session) == sessionMap.end() ) { sessionMap[session] = handler; @@ -488,7 +517,7 @@ void PluginManager::addHandlerInstance( SessionRef session, FileHandlerInstanceP void PluginManager::removeHandlerInstance( SessionRef session ) { if ( msPluginManager != 0 ) { - XMP_AutoLock(&sPluginManagerRWLock, kXMP_WriteLock); + XMP_AutoLock lock(&sSessionMapPluginManagerRWLock, kXMP_WriteLock); SessionMap & sessionMap = msPluginManager->mSessions; sessionMap.erase ( session ); } @@ -500,7 +529,7 @@ FileHandlerInstancePtr PluginManager::getHandlerInstance( SessionRef session ) { FileHandlerInstancePtr ret = 0; if ( msPluginManager != 0 ) { - XMP_AutoLock(&sPluginManagerRWLock, kXMP_ReadLock); + XMP_AutoLock lock(&sSessionMapPluginManagerRWLock, kXMP_ReadLock); ret = msPluginManager->mSessions[session]; } return ret; @@ -721,28 +750,6 @@ HostAPIRef PluginManager::getHostAPI( XMP_Uns32 version ) { 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; } @@ -758,7 +765,10 @@ void PluginManager::terminateHostAPI() switch( version ) { - case 1: + case 1: + case 2: + case 3: + case 4: { delete hostAPI->mFileIOAPI; delete hostAPI->mStrAPI; @@ -768,7 +778,7 @@ void PluginManager::terminateHostAPI() } break; - default: + default: { delete hostAPI; } @@ -776,4 +786,42 @@ void PluginManager::terminateHostAPI() } } +void PluginManager::initializeHostAPI() +{ + HostAPIRef hostAPI = NULL; + + + for ( int i = 0; i < XMP_HOST_API_VERSION_4; i++ ) + { + hostAPI = new HostAPI(); + hostAPI->mSize = sizeof( HostAPI ); + hostAPI->mVersion = i + 1; + + switch( hostAPI->mVersion ) + { + case 1: + SetupHostAPI_V1( hostAPI ); + break; + + case 2: + SetupHostAPI_V2( hostAPI ); + break; + + case 3: + SetupHostAPI_V3( hostAPI ); + break; + + default: + case 4: + SetupHostAPI_V4( hostAPI ); + break; + } + + if( hostAPI != NULL ) + { + msPluginManager->mHostAPIs[ hostAPI->mVersion ] = hostAPI; + } + } +} + } // namespace XMP_PLUGIN diff --git a/XMPFiles/source/PluginHandler/PluginManager.h b/XMPFiles/source/PluginHandler/PluginManager.h index 9b2240e..65165b7 100644 --- a/XMPFiles/source/PluginHandler/PluginManager.h +++ b/XMPFiles/source/PluginHandler/PluginManager.h @@ -12,17 +12,23 @@ #include "PluginHandler.h" #include "ModuleUtils.h" +#if XMP_WinBuild && _MSC_VER >= 1700 + // Visual Studio 2012 or newer supports C++11 (mostly) + #include <memory> + #include <functional> + #define XMP_SHARED_PTR std::shared_ptr +#else + #define XMP_SHARED_PTR std::tr1::shared_ptr +#endif + 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; +typedef XMP_SHARED_PTR<class Module> ModuleSharedPtr; +typedef XMP_SHARED_PTR<class FileHandler> FileHandlerSharedPtr; class FileHandlerInstance; typedef FileHandlerInstance* FileHandlerInstancePtr; @@ -37,8 +43,8 @@ inline void CheckError( WXMP_Error & error ) { if( error.mErrorID != kXMPErr_NoError ) { - if( error.mErrorID >= 500 /* kXMPErr_PluginInternal */ - || error.mErrorID <= 512 /* kXMPErr_SetHostAPI */ ) + if( error.mErrorID >= kXMPErr_PluginInternal && + error.mErrorID <= kXMPErr_PluginLastError ) { throw XMP_Error( kXMPErr_InternalFailure, error.mErrorMsg ); } @@ -52,7 +58,7 @@ inline void CheckError( WXMP_Error & error ) /** @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 + * At the initialization time of XMPFiles, PluginManager loads all the available plugins */ class PluginManager { @@ -136,6 +142,7 @@ private: /** * Terminate host API */ + void initializeHostAPI(); void terminateHostAPI(); /** @@ -143,7 +150,7 @@ private: * @param module * @return Void. */ - void loadResourceFile( ModuleSharedPtr module ); + void loadResourceFile( ModuleSharedPtr module ); /** * Scan mPluginDir for the plugins. It also scans nested folder upto level inMaxNumOfNestedFolder. @@ -164,21 +171,36 @@ private: const std::string& inPath, std::vector<std::string>& ioFoundLibs, XMP_Int32 inLevel, - XMP_Int32 inMaxNestingLevel ); + XMP_Int32 inMaxNestingLevel ); /** * Setup passed in HostAPI structure for the host API v1 */ static void SetupHostAPI_V1( HostAPIRef hostAPI ); + /** + * Setup passed in HostAPI structure for the host API v2 + */ + static void SetupHostAPI_V2( HostAPIRef hostAPI ); + + /** + * Setup passed in HostAPI structure for the host API v3 + */ + static void SetupHostAPI_V3( HostAPIRef hostAPI ); + + /** + * Setup passed in HostAPI structure for the host API v4 + */ + static void SetupHostAPI_V4( HostAPIRef hostAPI ); + + typedef std::map<XMP_FileFormat, FileHandlerPair> PluginHandlerMap; typedef std::map<XMP_Uns32, HostAPIRef> HostAPIMap; - typedef std::map<SessionRef, FileHandlerInstancePtr> SessionMap; + typedef std::map<SessionRef, FileHandlerInstancePtr> SessionMap; std::string mPluginDir; StringVec mExtensions; StringVec mPluginsNeeded; - ModuleVec mModules; PluginHandlerMap mHandlers; SessionMap mSessions; HostAPIMap mHostAPIs; diff --git a/XMPFiles/source/PluginHandler/XMPAtoms.cpp b/XMPFiles/source/PluginHandler/XMPAtoms.cpp index ed01027..8379c8b 100644 --- a/XMPFiles/source/PluginHandler/XMPAtoms.cpp +++ b/XMPFiles/source/PluginHandler/XMPAtoms.cpp @@ -60,6 +60,7 @@ const XMPAtomMapping kXMPAtomVec[] = { "kXMPFiles_NeedsReadOnlyPacket", kXMPFiles_NeedsReadOnlyPacket_K }, { "kXMPFiles_UsesSidecarXMP", kXMPFiles_UsesSidecarXMP_K }, { "kXMPFiles_FolderBasedFormat", kXMPFiles_FolderBasedFormat_K }, + { "kXMPFiles_NeedsPreloading", kXMPFiles_NeedsPreloading_K }, // Serialize option { "kXMP_OmitPacketWrapper", kXMP_OmitPacketWrapper_K }, @@ -88,7 +89,8 @@ void ResourceParser::clear() mFormatIDs.clear(); mCheckFormat.clear(); mHandler = FileHandlerSharedPtr(); - mFlags = mSerializeOption = mType = mVersion = 0; + mFlags = mSerializeOption = mType = 0; + mVersion = 0.0; } void ResourceParser::addHandler() @@ -103,7 +105,10 @@ void ResourceParser::addHandler() mHandler->setHandlerType( mType ); mHandler->setSerializeOption( mSerializeOption ); mHandler->setOverwriteHandler( mOverwriteHandler ); - if( mVersion != 0) mHandler->setVersion( mVersion ); + if( mVersion != 0.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". @@ -119,8 +124,6 @@ void ResourceParser::addHandler() 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(); @@ -144,7 +147,7 @@ bool ResourceParser::parseElementAttrs( const XML_Node * xmlNode, bool isTopLeve mHandler = FileHandlerSharedPtr( new FileHandler( this->mUID, 0, 0, mModule ) ); break; case Version_K: - this->mVersion = atoi( (*currAttr)->value.c_str() ); + sscanf( (*currAttr)->value.c_str(), "%lf", &this->mVersion ); break; case HandlerType_K: this->mType = getXMPAtomFromString( (*currAttr)->value ); @@ -153,7 +156,9 @@ bool ResourceParser::parseElementAttrs( const XML_Node * xmlNode, bool isTopLeve this->mOverwriteHandler = ( (*currAttr)->value == "true" ); break; default: - XMP_Throw( "Invalid Attr in Handler encountered in resource file", kXMPErr_Unavailable ); + //fix for bug 3565147: Plugin Architecture: If any unknown node is present in the Plugin Manifest then the Plugin is not loaded + //XMP_Throw( "Invalid Attr in Handler encountered in resource file", kXMPErr_Unavailable ); + break; } break; case CheckFormat_K: @@ -169,7 +174,9 @@ bool ResourceParser::parseElementAttrs( const XML_Node * xmlNode, bool isTopLeve this->mCheckFormat.mByteSeq = (*currAttr)->value; break; default: - XMP_Throw( "Invalid Attr in CheckFormat encountered in resource file", kXMPErr_Unavailable ); + //fix for bug 3565147: Plugin Architecture: If any unknown node is present in the Plugin Manifest then the Plugin is not loaded + //XMP_Throw( "Invalid Attr in CheckFormat encountered in resource file", kXMPErr_Unavailable ); + break; } break; case Extension_K: @@ -179,7 +186,9 @@ bool ResourceParser::parseElementAttrs( const XML_Node * xmlNode, bool isTopLeve this->mFileExtensions.insert( HandlerRegistry::getInstance().getFileFormat( (*currAttr)->value, true) ); break; default: - XMP_Throw( "Invalid Attr in Extension encountered in resource file", kXMPErr_Unavailable ); + //fix for bug 3565147: Plugin Architecture: If any unknown node is present in the Plugin Manifest then the Plugin is not loaded + //XMP_Throw( "Invalid Attr in Extension encountered in resource file", kXMPErr_Unavailable ); + break; } break; case FormatID_K: @@ -196,7 +205,9 @@ bool ResourceParser::parseElementAttrs( const XML_Node * xmlNode, bool isTopLeve this->mFormatIDs.insert( formatID ); break; default: - XMP_Throw( "Invalid Attr in FormatID encountered in resource file", kXMPErr_Unavailable ); + //fix for bug 3565147: Plugin Architecture: If any unknown node is present in the Plugin Manifest then the Plugin is not loaded + //XMP_Throw( "Invalid Attr in FormatID encountered in resource file", kXMPErr_Unavailable ); + break; } break; case HandlerFlag_K: @@ -204,12 +215,15 @@ bool ResourceParser::parseElementAttrs( const XML_Node * xmlNode, bool isTopLeve { case Name_K: oneflag = getHandlerFlag( (*currAttr)->value ); - if( 0 == oneflag ) - XMP_Throw( "Invalid handler flag found in resource file...", kXMPErr_Unavailable ); + //fix for bug 3565147: Plugin Architecture: If any unknown node is present in the Plugin Manifest then the Plugin is not loaded + //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 ); + //fix for bug 3565147: Plugin Architecture: If any unknown node is present in the Plugin Manifest then the Plugin is not loaded + //XMP_Throw( "Invalid handler flag found in resource file...", kXMPErr_Unavailable ); + break; } break; case SerializeOption_K: @@ -217,12 +231,15 @@ bool ResourceParser::parseElementAttrs( const XML_Node * xmlNode, bool isTopLeve { case Name_K: oneflag = getSerializeOption( (*currAttr)->value ); - if( 0 == oneflag ) - XMP_Throw( "Invalid serialize option found in resource file...", kXMPErr_Unavailable ); + //fix for bug 3565147: Plugin Architecture: If any unknown node is present in the Plugin Manifest then the Plugin is not loaded + //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 ); + //fix for bug 3565147: Plugin Architecture: If any unknown node is present in the Plugin Manifest then the Plugin is not loaded + //XMP_Throw( "Invalid handler flag found in resource file...", kXMPErr_Unavailable ); + break; } break; default: @@ -330,8 +347,11 @@ XMP_OptionBits ResourceParser::getHandlerFlag( const std::string & stringAtom ) return kXMPFiles_UsesSidecarXMP; case kXMPFiles_FolderBasedFormat_K: return kXMPFiles_FolderBasedFormat; + case kXMPFiles_NeedsPreloading_K: + return kXMPFiles_NeedsPreloading; default: - XMP_Throw( "Invalid PluginhandlerFlag ...", kXMPErr_Unavailable ); + //fix for bug 3565147: Plugin Architecture: If any unknown node is present in the Plugin Manifest then the Plugin is not loaded + //XMP_Throw( "Invalid PluginhandlerFlag ...", kXMPErr_Unavailable ); return 0; } } @@ -375,7 +395,8 @@ XMP_OptionBits ResourceParser::getSerializeOption( const std::string & stringAto case kXMP_EncodeUTF32Little_K: return kXMP_EncodeUTF32Little; default: - XMP_Throw( "Invalid Serialize Option ...", kXMPErr_Unavailable ); + //fix for bug 3565147: Plugin Architecture: If any unknown node is present in the Plugin Manifest then the Plugin is not loaded + //XMP_Throw( "Invalid Serialize Option ...", kXMPErr_Unavailable ); return 0; } } diff --git a/XMPFiles/source/PluginHandler/XMPAtoms.h b/XMPFiles/source/PluginHandler/XMPAtoms.h index 5004008..4cacef0 100644 --- a/XMPFiles/source/PluginHandler/XMPAtoms.h +++ b/XMPFiles/source/PluginHandler/XMPAtoms.h @@ -59,6 +59,7 @@ enum kXMPFiles_NeedsReadOnlyPacket_K, kXMPFiles_UsesSidecarXMP_K, kXMPFiles_FolderBasedFormat_K, + kXMPFiles_NeedsPreloading_K, // Serialize option kXMP_OmitPacketWrapper_K, @@ -99,7 +100,7 @@ class ResourceParser { public: ResourceParser(ModuleSharedPtr module) - : mModule(module), mFlags(0), mSerializeOption(0), mType(0), mVersion(0), mOverwriteHandler(false) {} + : mModule(module), mFlags(0), mSerializeOption(0), mType(0), mVersion(0.0), mOverwriteHandler(false) {} /** * Initialize the XMPAtoms which will be used in parsing resource files. @@ -186,7 +187,7 @@ private: FileHandlerType mType; XMP_OptionBits mFlags; XMP_OptionBits mSerializeOption; - XMP_Uns32 mVersion; + double mVersion; bool mOverwriteHandler; CheckFormat mCheckFormat; std::set<XMP_FileFormat> mFileExtensions; diff --git a/XMPFiles/source/WXMPFiles.cpp b/XMPFiles/source/WXMPFiles.cpp index f0f7b52..26fdcac 100644 --- a/XMPFiles/source/WXMPFiles.cpp +++ b/XMPFiles/source/WXMPFiles.cpp @@ -12,6 +12,8 @@ #include "public/include/client-glue/WXMPFiles.hpp" +#include "source/XMP_ProgressTracker.hpp" + #include "XMPFiles/source/XMPFiles_Impl.hpp" #include "XMPFiles/source/XMPFiles.hpp" @@ -125,7 +127,7 @@ void WXMPFiles_DecrementRefCount_1 ( XMPFilesRef xmpObjRef ) XMP_EXIT_NoThrow } -// ================================================================================================= +// ------------------------------------------------------------------------------------------------- void WXMPFiles_GetFormatInfo_1 ( XMP_FileFormat format, XMP_OptionBits * flags, @@ -138,7 +140,7 @@ void WXMPFiles_GetFormatInfo_1 ( XMP_FileFormat format, XMP_EXIT } -// ================================================================================================= +// ------------------------------------------------------------------------------------------------- void WXMPFiles_CheckFileFormat_1 ( XMP_StringPtr filePath, WXMP_Result * wResult ) @@ -150,7 +152,7 @@ void WXMPFiles_CheckFileFormat_1 ( XMP_StringPtr filePath, XMP_EXIT } -// ================================================================================================= +// ------------------------------------------------------------------------------------------------- void WXMPFiles_CheckPackageFormat_1 ( XMP_StringPtr folderPath, WXMP_Result * wResult ) @@ -162,7 +164,7 @@ void WXMPFiles_CheckPackageFormat_1 ( XMP_StringPtr folderPath, XMP_EXIT } -// ================================================================================================= +// ------------------------------------------------------------------------------------------------- void WXMPFiles_GetFileModDate_1 ( XMP_StringPtr filePath, XMP_DateTime * modDate, @@ -177,6 +179,47 @@ void WXMPFiles_GetFileModDate_1 ( XMP_StringPtr filePath, XMP_EXIT } +// ------------------------------------------------------------------------------------------------- + +void WXMPFiles_GetAssociatedResources_1 ( XMP_StringPtr filePath, + void * resourceList, + XMP_FileFormat format, + XMP_OptionBits options, + SetClientStringVectorProc SetClientStringVector, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPFiles_GetAssociatedResources_1" ) + + if ( resourceList == 0 ) XMP_Throw ( "An result resource list vector must be provided", kXMPErr_BadParam ); + + std::vector<std::string> resList; // Pass a local vector, not the client's. + (*SetClientStringVector) ( resourceList, 0, 0 ); // Clear the client's result vector. + wResult->int32Result = XMPFiles::GetAssociatedResources ( filePath, &resList, format, options ); + + if ( wResult->int32Result && (! resList.empty()) ) { + const size_t fileCount = resList.size(); + std::vector<XMP_StringPtr> ptrArray; + ptrArray.reserve ( fileCount ); + for ( size_t i = 0; i < fileCount; ++i ) ptrArray.push_back ( resList[i].c_str() ); + (*SetClientStringVector) ( resourceList, ptrArray.data(), fileCount ); + } + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void WXMPFiles_IsMetadataWritable_1 ( XMP_StringPtr filePath, + XMP_Bool * writable, + XMP_FileFormat format, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPFiles_IsMetadataWritable_1" ) + wResult->int32Result = XMPFiles::IsMetadataWritable ( filePath, writable, format, options ); + XMP_EXIT +} + + // ================================================================================================= void WXMPFiles_OpenFile_1 ( XMPFilesRef xmpObjRef, @@ -340,6 +383,85 @@ void WXMPFiles_CanPutXMP_1 ( XMPFilesRef xmpObjRef, // ================================================================================================= +void WXMPFiles_SetDefaultProgressCallback_1 ( XMP_ProgressReportWrapper wrapperProc, + XMP_ProgressReportProc clientProc, + void * context, + float interval, + XMP_Bool sendStartStop, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPFiles_SetDefaultProgressCallback_1" ) + + XMP_ProgressTracker::CallbackInfo cbInfo ( wrapperProc, clientProc, context, interval, ConvertXMP_BoolToBool( sendStartStop ) ); + XMPFiles::SetDefaultProgressCallback ( cbInfo ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void WXMPFiles_SetProgressCallback_1 ( XMPFilesRef xmpObjRef, + XMP_ProgressReportWrapper wrapperProc, + XMP_ProgressReportProc clientProc, + void * context, + float interval, + XMP_Bool sendStartStop, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_SetProgressCallback_1" ) + + XMP_ProgressTracker::CallbackInfo cbInfo ( wrapperProc, clientProc, context, interval, ConvertXMP_BoolToBool( sendStartStop) ); + thiz->SetProgressCallback ( cbInfo ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void WXMPFiles_SetDefaultErrorCallback_1 ( XMPFiles_ErrorCallbackWrapper wrapperProc, + XMPFiles_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPFiles_SetDefaultErrorCallback_1" ) + + XMPFiles::SetDefaultErrorCallback ( wrapperProc, clientProc, context, limit ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void WXMPFiles_SetErrorCallback_1 ( XMPFilesRef xmpObjRef, + XMPFiles_ErrorCallbackWrapper wrapperProc, + XMPFiles_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_SetErrorCallback_1" ) + + thiz->SetErrorCallback ( wrapperProc, clientProc, context, limit ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void WXMPFiles_ResetErrorCallbackLimit_1 ( XMPFilesRef xmpObjRef, + XMP_Uns32 limit, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_ResetErrorCallbackLimit_1" ) + + thiz->ResetErrorCallbackLimit ( limit ); + + XMP_EXIT +} + +// ================================================================================================= + #if __cplusplus } #endif diff --git a/XMPFiles/source/XMPFiles.cpp b/XMPFiles/source/XMPFiles.cpp index b720725..32ea41f 100644 --- a/XMPFiles/source/XMPFiles.cpp +++ b/XMPFiles/source/XMPFiles.cpp @@ -20,7 +20,10 @@ #include "XMPFiles/source/XMPFiles_Impl.hpp" #include "XMPFiles/source/HandlerRegistry.h" -#include "XMPFiles/source/PluginHandler/PluginManager.h" + +#if EnablePluginManager + #include "XMPFiles/source/PluginHandler/PluginManager.h" +#endif #include "XMPFiles/source/FormatSupport/ID3_Support.hpp" @@ -37,12 +40,19 @@ // ================================================================================================= using namespace Common; -using namespace XMP_PLUGIN; + +#if EnablePluginManager + using namespace XMP_PLUGIN; +#endif // ================================================================================================= XMP_Int32 sXMPFilesInitCount = 0; +static XMP_ProgressTracker::CallbackInfo sProgressDefault; +static XMPFiles::ErrorCallbackInfo sDefaultErrorCallback; + + #if GatherPerformanceData APIPerfCollection* sAPIPerf = 0; #endif @@ -65,6 +75,17 @@ XMP_Int32 sXMPFilesInitCount = 0; const char * kXMPFiles_EmbeddedVersion = kXMPFiles_VersionMessage; const char * kXMPFiles_EmbeddedCopyright = kXMPFilesName " " kXMP_CopyrightStr; +#define EMPTY_FILE_PATH "" +#define XMP_FILES_STATIC_START try { int a; +#define XMP_FILES_STATIC_END1(severity) a = 1; } catch ( XMP_Error & error ) { sDefaultErrorCallback.NotifyClient ( (severity), error, EMPTY_FILE_PATH ); } +#define XMP_FILES_STATIC_END2(filePath, severity) a = 1; } catch ( XMP_Error & error ) { sDefaultErrorCallback.NotifyClient ( (severity), error, (filePath) ); } +#define XMP_FILES_START try { int b; +#define XMP_FILES_END1(severity) b = 1; } catch ( XMP_Error & error ) { errorCallback.NotifyClient ( (severity), error, this->filePath.c_str() ); } +#define XMP_FILES_END2(filePath, severity) b = 1; } catch ( XMP_Error & error ) { errorCallback.NotifyClient ( (severity), error, (filePath) ); } +#define XMP_FILES_STATIC_NOTIFY_ERROR(errorCallbackPtr, filePath, severity, error) \ + if ( (errorCallbackPtr) != NULL ) (errorCallbackPtr)->NotifyClient ( (severity), (error), (filePath) ); + + // ================================================================================================= #if EnablePacketScanning @@ -78,7 +99,7 @@ const char * kXMPFiles_EmbeddedCopyright = kXMPFilesName " " kXMP_CopyrightStr; void XMPFiles::GetVersionInfo ( XMP_VersionInfo * info ) { - + XMP_FILES_STATIC_START memset ( info, 0, sizeof(XMP_VersionInfo) ); info->major = XMPFILES_API_VERSION_MAJOR; @@ -87,7 +108,7 @@ XMPFiles::GetVersionInfo ( XMP_VersionInfo * info ) info->isDebug = kXMPFiles_DebugFlag; info->flags = 0; // ! None defined yet. info->message = kXMPFiles_VersionMessage; - + XMP_FILES_STATIC_END1 ( kXMPErrSev_OperationFatal ) } // XMPFiles::GetVersionInfo // ================================================================================================= @@ -104,6 +125,7 @@ XMPFiles::GetVersionInfo ( XMP_VersionInfo * info ) bool XMPFiles::Initialize( XMP_OptionBits options, const char* pluginFolder, const char* plugins /* = NULL */ ) { + XMP_FILES_STATIC_START ++sXMPFilesInitCount; if ( sXMPFilesInitCount > 1 ) return true; @@ -145,11 +167,13 @@ XMPFiles::Initialize( XMP_OptionBits options, const char* pluginFolder, const ch 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. - } + #if EnablePluginManager + if ( pluginFolder != 0 ) { + std::string pluginList; + if ( plugins != 0 ) pluginList.assign ( plugins ); + PluginManager::initialize ( pluginFolder, pluginList ); // Load file handler plugins. + } + #endif // Make sure the embedded info strings are referenced and kept. if ( (kXMPFiles_EmbeddedVersion[0] == 0) || (kXMPFiles_EmbeddedCopyright[0] == 0) ) return false; @@ -162,7 +186,8 @@ XMPFiles::Initialize( XMP_OptionBits options, const char* pluginFolder, const ch XMP_Assert ( sizeof(XMP_Uns16) == 2 ); XMP_Assert ( sizeof(XMP_Uns32) == 4 ); XMP_Assert ( sizeof(XMP_Uns64) == 8 ); - + XMP_Assert ( sizeof(XMP_Bool) == 1 ); + XMP_FILES_STATIC_END1 ( kXMPErrSev_ProcessFatal ) return true; } // XMPFiles::Initialize @@ -248,6 +273,7 @@ static void ReportPerformanceData() void XMPFiles::Terminate() { + XMP_FILES_STATIC_START --sXMPFilesInitCount; if ( sXMPFilesInitCount != 0 ) return; // Not ready to terminate, or already terminated. @@ -256,7 +282,10 @@ XMPFiles::Terminate() EliminateGlobal ( sAPIPerf ); #endif - PluginManager::terminate(); + #if EnablePluginManager + PluginManager::terminate(); + #endif + HandlerRegistry::terminate(); SXMPMeta::Terminate(); // Just in case the client does not. @@ -273,23 +302,36 @@ XMPFiles::Terminate() xmpFilesLog = stderr; #endif + XMP_FILES_STATIC_END1 ( kXMPErrSev_ProcessFatal ) } // XMPFiles::Terminate // ================================================================================================= -XMPFiles::XMPFiles() : - clientRefs(0), - format(kXMP_UnknownFile), - ioRef(0), - openFlags(0), - handler(0), - tempPtr(0), - tempUI32(0), - abortProc(0), - abortArg(0) +XMPFiles::XMPFiles() + : clientRefs(0) + , format(kXMP_UnknownFile) + , ioRef(0) + , openFlags(0) + , handler(0) + , tempPtr(0) + , tempUI32(0) + , abortProc(0) + , abortArg(0) + , progressTracker(0) { - // Nothing more to do, clientRefs is incremented in wrapper. + XMP_FILES_START + if ( sProgressDefault.clientProc != 0 ) { + this->progressTracker = new XMP_ProgressTracker ( sProgressDefault ); + if (this->progressTracker == 0) XMP_Throw ( "XMPFiles: Unable to allocate memory for Progress Tracker", kXMPErr_NoMemory ); + } + if ( sDefaultErrorCallback.clientProc != 0 ) { + this->errorCallback.wrapperProc = sDefaultErrorCallback.wrapperProc; + this->errorCallback.clientProc = sDefaultErrorCallback.clientProc; + this->errorCallback.context = sDefaultErrorCallback.context; + this->errorCallback.limit = sDefaultErrorCallback.limit; + } + XMP_FILES_END1 ( kXMPErrSev_OperationFatal ); } // XMPFiles::XMPFiles // ================================================================================================= @@ -314,6 +356,7 @@ static inline void CloseLocalFile ( XMPFiles* thiz ) XMPFiles::~XMPFiles() { + XMP_FILES_START XMP_Assert ( this->clientRefs <= 0 ); if ( this->handler != 0 ) { @@ -323,7 +366,9 @@ XMPFiles::~XMPFiles() CloseLocalFile ( this ); + if ( this->progressTracker != 0 ) delete this->progressTracker; if ( this->tempPtr != 0 ) free ( this->tempPtr ); // ! Must have been malloc-ed! + XMP_FILES_END1 ( kXMPErrSev_OperationFatal ) } // XMPFiles::~XMPFiles @@ -334,8 +379,10 @@ bool XMPFiles::GetFormatInfo ( XMP_FileFormat format, XMP_OptionBits * flags /* = 0 */ ) { - + XMP_FILES_STATIC_START return HandlerRegistry::getInstance().getFormatInfo ( format, flags ); + XMP_FILES_STATIC_END1 ( kXMPErrSev_OperationFatal ) + return false; } // XMPFiles::GetFormatInfo @@ -345,14 +392,23 @@ XMPFiles::GetFormatInfo ( XMP_FileFormat format, XMP_FileFormat XMPFiles::CheckFileFormat ( XMP_StringPtr clientPath ) { + XMP_FILES_STATIC_START 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 ); + bogus.SetFilePath ( 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; + if ( handlerInfo == 0 ) { + if ( !Host_IO::Exists ( clientPath ) ) { + XMP_Error error ( kXMPErr_NoFile, "XMPFiles: file does not exist" ); + XMP_FILES_STATIC_NOTIFY_ERROR ( &sDefaultErrorCallback, clientPath, kXMPErrSev_Recoverable, error ); + } + return kXMP_UnknownFile; + } return handlerInfo->format; + XMP_FILES_STATIC_END2 ( clientPath, kXMPErrSev_OperationFatal ) + return kXMP_UnknownFile; } // XMPFiles::CheckFileFormat @@ -362,6 +418,7 @@ XMPFiles::CheckFileFormat ( XMP_StringPtr clientPath ) XMP_FileFormat XMPFiles::CheckPackageFormat ( XMP_StringPtr folderPath ) { + XMP_FILES_STATIC_START // 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. @@ -375,17 +432,29 @@ XMPFiles::CheckPackageFormat ( XMP_StringPtr folderPath ) if ( folderMode != Host_IO::kFMode_IsFolder ) return kXMP_UnknownFile; return HandlerRegistry::checkTopFolderName ( std::string ( folderPath ) ); #endif + XMP_FILES_STATIC_END2 ( folderPath, kXMPErrSev_OperationFatal ) + return kXMP_UnknownFile; } // XMPFiles::CheckPackageFormat // ================================================================================================= -static bool FileIsExcluded ( XMP_StringPtr clientPath, std::string * fileExt, Host_IO::FileMode * clientMode ) +static bool FileIsExcluded ( + XMP_StringPtr clientPath, + std::string * fileExt, + Host_IO::FileMode * clientMode, + const XMPFiles::ErrorCallbackInfo * _errorCallbackInfoPtr = NULL ) { // ! 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; + + if ( (*clientMode == Host_IO::kFMode_IsFolder) || (*clientMode == Host_IO::kFMode_IsOther) ) { + XMP_Error error ( kXMPErr_FilePathNotAFile, "XMPFiles: path specified is not a file" ); + XMP_FILES_STATIC_NOTIFY_ERROR ( _errorCallbackInfoPtr, clientPath, kXMPErrSev_Recoverable, error ); + return true; + } + XMP_Assert ( (*clientMode == Host_IO::kFMode_IsFile) || (*clientMode == Host_IO::kFMode_DoesNotExist) ); if ( *clientMode == Host_IO::kFMode_IsFile ) { @@ -401,7 +470,11 @@ static bool FileIsExcluded ( XMP_StringPtr clientPath, std::string * fileExt, Ho // 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; + if ( *fileExt == kKnownRejectedFiles[i] ) { + XMP_Error error ( kXMPErr_RejectedFileExtension, "XMPFiles: rejected file extension specified" ); + XMP_FILES_STATIC_NOTIFY_ERROR ( _errorCallbackInfoPtr, clientPath, kXMPErrSev_Recoverable, error ); + return true; + } } } @@ -410,6 +483,38 @@ static bool FileIsExcluded ( XMP_StringPtr clientPath, std::string * fileExt, Ho } // FileIsExcluded +static XMPFileHandlerInfo* CreateFileHandlerInfo ( + XMPFiles* dummyParent, + XMP_FileFormat * format, + XMP_OptionBits options, + const XMPFiles::ErrorCallbackInfo * _errorCallbackInfoPtr = NULL ) +{ + Host_IO::FileMode clientMode; + std::string fileExt; // Used to check for excluded files. + bool excluded = FileIsExcluded ( dummyParent->GetFilePath().c_str(), &fileExt, &clientMode, &sDefaultErrorCallback ); // ! Fills in fileExt and clientMode. + if ( excluded ) return 0; + + XMPFileHandlerInfo * handlerInfo = 0; + + XMP_FileFormat dummyFormat = kXMP_UnknownFile; + if ( format == 0 ) format = &dummyFormat; + + options |= kXMPFiles_OpenForRead; + handlerInfo = HandlerRegistry::getInstance().selectSmartHandler ( dummyParent, dummyParent->GetFilePath().c_str(), *format, options ); + + if ( handlerInfo == 0 ) { + if ( clientMode == Host_IO::kFMode_DoesNotExist ) { + XMP_Error error ( kXMPErr_NoFile, "XMPFiles: file does not exist" ); + XMP_FILES_STATIC_NOTIFY_ERROR ( _errorCallbackInfoPtr, dummyParent->GetFilePath().c_str(), kXMPErrSev_Recoverable, error ); + }else{ + XMP_Error error ( kXMPErr_NoFileHandler, "XMPFiles: No smart file handler available to handle file" ); + XMP_FILES_STATIC_NOTIFY_ERROR ( _errorCallbackInfoPtr, dummyParent->GetFilePath().c_str(), kXMPErrSev_Recoverable, error ); + } + return 0; + } + return handlerInfo; +} + // ================================================================================================= /* class static */ @@ -419,45 +524,167 @@ XMPFiles::GetFileModDate ( XMP_StringPtr clientPath, XMP_FileFormat * format, /* = 0 */ XMP_OptionBits options /* = 0 */ ) { - + XMP_FILES_STATIC_START // --------------------------------------------------------------- // 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; + dummyParent.SetFilePath ( clientPath ); + + + XMPFileHandlerInfo * handlerInfo = 0; + handlerInfo = CreateFileHandlerInfo ( &dummyParent, format, options, &sDefaultErrorCallback ); + if ( handlerInfo == 0 ) return false; + + // ------------------------------------------------------------------------- + // Fill in the format output. Call the handler to get the modification date. - 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; + dummyParent.format = handlerInfo->format; + if ( format ) *format = handlerInfo->format; + + dummyParent.handler = handlerInfo->handlerCTor ( &dummyParent ); + + bool ok = false; + + std::vector <std::string> resourceList; + try{ + XMP_DateTime lastModDate; + XMP_DateTime junkDate; + if ( modDate == 0 ) modDate = &junkDate; + dummyParent.handler->FillAssociatedResources ( &resourceList ); + size_t countRes = resourceList.size(); + for ( size_t index = 0; index < countRes ; ++index ){ + XMP_StringPtr curFilePath = resourceList[index].c_str(); + if( Host_IO::GetFileMode ( curFilePath ) != Host_IO::kFMode_IsFile ) continue;// only interested in files + Host_IO::GetModifyDate ( curFilePath, &lastModDate ); + if ( ! ok || ( SXMPUtils::CompareDateTime ( *modDate , lastModDate ) < 0 ) ) + { + *modDate = lastModDate; + ok = true; + } + } + } + catch(...){ + // Fallback to the old way + // eventually this method would go away as + // soon as the implementation for + // GetAssociatedResources is added to all + // the file/Plugin Handlers + ok = dummyParent.handler->GetFileModDate ( modDate ); + } + delete dummyParent.handler; + dummyParent.handler = 0; + + return ok; + XMP_FILES_STATIC_END2 ( clientPath, kXMPErrSev_OperationFatal ) + return false; + +} // XMPFiles::GetFileModDate + +// ================================================================================================= + +/* class static */ +bool +XMPFiles::GetAssociatedResources ( + XMP_StringPtr filePath, + std::vector<std::string> * resourceList, + XMP_FileFormat format /* = kXMP_UnknownFile */, + XMP_OptionBits options /* = 0 */ ) +{ + XMP_FILES_STATIC_START + XMP_Assert ( (resourceList != 0) && resourceList->empty() ); // Ensure that the glue passes in an empty local. + + // Try to select a handler. + + if ( (filePath == 0) || (*filePath == 0) ) return false; + + XMPFiles dummyParent; // GetAssociatedResources is static, but the handler needs a parent. + dummyParent.SetFilePath ( filePath ); XMPFileHandlerInfo * handlerInfo = 0; - XMPFileHandlerCTor handlerCTor = 0; - XMP_OptionBits handlerFlags = 0; + handlerInfo = CreateFileHandlerInfo ( &dummyParent, &format, options, &sDefaultErrorCallback ); + if ( handlerInfo == 0 ) return false; + + // ------------------------------------------------------------------------- + // Fill in the format output. Call the handler to get the Associated Resources. + + dummyParent.format = handlerInfo->format; + + dummyParent.handler = handlerInfo->handlerCTor ( &dummyParent ); - XMP_FileFormat dummyFormat = kXMP_UnknownFile; - if ( format == 0 ) format = &dummyFormat; + try { + dummyParent.handler->FillAssociatedResources ( resourceList ); + } catch ( XMP_Error& error ) { + if ( error.GetID() == kXMPErr_Unimplemented ) { + XMP_FILES_STATIC_NOTIFY_ERROR ( &sDefaultErrorCallback, filePath, kXMPErrSev_Recoverable, error ); + return false; + } else { + throw; + } + } + XMP_Assert ( ! resourceList->empty() ); - options |= kXMPFiles_OpenForRead; - handlerInfo = HandlerRegistry::getInstance().selectSmartHandler ( &dummyParent, clientPath, *format, options ); - if ( handlerInfo == 0 ) return false; + delete dummyParent.handler; + dummyParent.handler = 0; - // ------------------------------------------------------------------------- - // Fill in the format output. Call the handler to get the modification date. + return true; + + XMP_FILES_STATIC_END2 ( filePath, kXMPErrSev_OperationFatal ) + return false; + +} // XMPFiles::GetAssociatedResources + +bool +XMPFiles::IsMetadataWritable ( + XMP_StringPtr filePath, + XMP_Bool * writable, + XMP_FileFormat format /* = kXMP_UnknownFile */, + XMP_OptionBits options /* = 0 */ ) +{ + XMP_FILES_STATIC_START + // Try to select a handler. - dummyParent.format = handlerInfo->format; - *format = handlerInfo->format; + if ( (filePath == 0) || (*filePath == 0) ) return false; + XMPFiles dummyParent; // IsMetadataWritable is static, but the handler needs a parent. + dummyParent.SetFilePath ( filePath ); + + XMPFileHandlerInfo * handlerInfo = 0; + handlerInfo = CreateFileHandlerInfo ( &dummyParent, &format, options, &sDefaultErrorCallback ); + if ( handlerInfo == 0 ) return false; + + if ( writable == 0 ) { + XMP_Throw("Boolean parameter is required for IsMetadataWritable() API.", kXMPErr_BadParam); + } else { + *writable = kXMP_Bool_False; + } + + + dummyParent.format = handlerInfo->format; dummyParent.handler = handlerInfo->handlerCTor ( &dummyParent ); - bool ok = dummyParent.handler->GetFileModDate ( modDate ); + // We don't require any of the files to be opened at this point. + // Also, if We don't close them then this will be a problem for embedded handlers because we will be checking + // write permission on the same file which could be open (in some mode) already. + CloseLocalFile(&dummyParent); + try { + *writable = ConvertBoolToXMP_Bool( dummyParent.handler->IsMetadataWritable() ); + } + catch ( XMP_Error& error ) { + if ( error.GetID() == kXMPErr_Unimplemented ) { + XMP_FILES_STATIC_NOTIFY_ERROR ( &sDefaultErrorCallback, filePath, kXMPErrSev_Recoverable, error ); + return false; + } else { + throw; + } + } delete dummyParent.handler; dummyParent.handler = 0; - - return ok; - -} // XMPFiles::GetFileModDate + XMP_FILES_STATIC_END2 ( filePath, kXMPErrSev_OperationFatal ) + return true; +} // XMPFiles::IsMetadataWritable + // ================================================================================================= @@ -476,7 +703,7 @@ DoOpenFile ( XMPFiles * thiz, CloseLocalFile ( thiz ); // Sanity checks if prior call failed. thiz->ioRef = clientIO; - thiz->filePath = clientPath; + thiz->SetFilePath ( clientPath ); thiz->format = kXMP_UnknownFile; // Make sure it is preset for later check. thiz->openFlags = openFlags; @@ -489,7 +716,7 @@ DoOpenFile ( XMPFiles * thiz, if ( thiz->UsesClientIO() ) { clientMode = Host_IO::kFMode_IsFile; } else { - bool excluded = FileIsExcluded ( clientPath, &fileExt, &clientMode ); // ! Fills in fileExt and clientMode. + bool excluded = FileIsExcluded ( clientPath, &fileExt, &clientMode, &thiz->errorCallback ); // ! Fills in fileExt and clientMode. if ( excluded ) return false; } @@ -505,16 +732,32 @@ DoOpenFile ( XMPFiles * thiz, #if ! EnablePacketScanning - if ( handlerInfo == 0 ) return false; + if ( handlerInfo == 0 ) { + if ( clientMode == Host_IO::kFMode_DoesNotExist ) { + XMP_Error error ( kXMPErr_NoFile, "XMPFiles: file does not exist" ); + XMP_FILES_STATIC_NOTIFY_ERROR ( &thiz->errorCallback, clientPath, kXMPErrSev_Recoverable, error ); + } + return false; + } #else if ( handlerInfo == 0 ) { // No smart handler, packet scan if appropriate. + if ( clientMode == Host_IO::kFMode_DoesNotExist ) { + XMP_Error error ( kXMPErr_NoFile, "XMPFiles: file does not exist" ); + XMP_FILES_STATIC_NOTIFY_ERROR ( &thiz->errorCallback, clientPath, kXMPErrSev_Recoverable, error ); + return false; + } else if ( clientMode != Host_IO::kFMode_IsFile ) { + return false; + } - if ( clientMode != Host_IO::kFMode_IsFile ) return false; - if ( openFlags & kXMPFiles_OpenUseSmartHandler ) return false; + if ( openFlags & kXMPFiles_OpenUseSmartHandler ) { + XMP_Error error ( kXMPErr_NoFileHandler, "XMPFiles: No smart file handler available to handle file" ); + XMP_FILES_STATIC_NOTIFY_ERROR ( &thiz->errorCallback, clientPath, kXMPErrSev_Recoverable, error ); + return false; + } if ( openFlags & kXMPFiles_OpenLimitedScanning ) { bool scanningOK = false; @@ -588,7 +831,7 @@ static bool DoOpenFile( XMPFiles* thiz, // setup members // thiz->ioRef = clientIO; - thiz->filePath = std::string( clientPath ); + thiz->SetFilePath ( clientPath ); thiz->format = hdlInfo.format; thiz->openFlags = openFlags; @@ -602,7 +845,12 @@ static bool DoOpenFile( XMPFiles* thiz, XMP_Assert ( handlerFlags == handler->handlerFlags ); thiz->handler = handler; + bool readOnly = XMP_OptionIsClear ( openFlags, kXMPFiles_OpenForUpdate ); + if ( thiz->ioRef == 0 ) { //Need to open the file if not done already + thiz->ioRef = XMPFiles_IO::New_XMPFiles_IO ( clientPath, readOnly ); + if ( thiz->ioRef == 0 ) return false; + } // // try to read metadata // @@ -644,9 +892,10 @@ XMPFiles::OpenFile ( XMP_StringPtr clientPath, XMP_FileFormat format /* = kXMP_UnknownFile */, XMP_OptionBits openFlags /* = 0 */ ) { - + XMP_FILES_START return DoOpenFile ( this, 0, clientPath, format, openFlags ); - + XMP_FILES_END2 ( clientPath, kXMPErrSev_FileFatal ) + return false; } // ================================================================================================= @@ -658,35 +907,38 @@ XMPFiles::OpenFile ( XMP_IO* clientIO, XMP_FileFormat format /* = kXMP_UnknownFile */, XMP_OptionBits openFlags /* = 0 */ ) { - + XMP_FILES_START + this->progressTracker = 0; // Progress tracking is not supported for client-managed I/O. return DoOpenFile ( this, clientIO, "", format, openFlags ); + XMP_FILES_END1 ( kXMPErrSev_FileFatal ) + return false; } // 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, +bool XMPFiles::OpenFile ( const Common::XMPFileHandlerInfo& hdlInfo, XMP_IO* clientIO, XMP_OptionBits openFlags /*= 0*/ ) { + XMP_FILES_START + this->progressTracker = 0; // Progress tracking is not supported for client-managed I/O. + return DoOpenFile ( this, hdlInfo, clientIO, NULL, openFlags ); + XMP_FILES_END1 ( kXMPErrSev_FileFatal ) + return false; - return DoOpenFile( this, hdlInfo, clientIO, NULL, openFlags ); } #endif // XMP_StaticBuild // ================================================================================================= -bool XMPFiles::OpenFile( const Common::XMPFileHandlerInfo& hdlInfo, +bool XMPFiles::OpenFile ( const Common::XMPFileHandlerInfo& hdlInfo, XMP_StringPtr filePath, XMP_OptionBits openFlags /*= 0*/ ) { - - return DoOpenFile( this, hdlInfo, NULL, filePath, openFlags ); + XMP_FILES_START + return DoOpenFile ( this, hdlInfo, NULL, filePath, openFlags ); + XMP_FILES_END2 (filePath, kXMPErrSev_FileFatal ) + return false; } // ================================================================================================= @@ -694,6 +946,7 @@ bool XMPFiles::OpenFile( const Common::XMPFileHandlerInfo& hdlInfo, void XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) { + XMP_FILES_START if ( this->handler == 0 ) return; // Return if there is no open file (not an error). bool needsUpdate = this->handler->needsUpdate; @@ -716,6 +969,11 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) XMP_Throw ( "XMPFiles::CloseFile - Safe update not supported", kXMPErr_Unavailable ); } + if ( (this->progressTracker != 0) && this->UsesLocalIO() ) { + XMPFiles_IO * localFile = (XMPFiles_IO*)this->ioRef; + localFile->SetProgressTracker ( this->progressTracker ); + } + // Try really hard to make sure the file is closed and the handler is deleted. try { @@ -765,6 +1023,7 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) XMP_IO* origFileRef = this->ioRef; origFileRef->Rewind(); + if ( this->progressTracker != 0 && (this->handler->handlerFlags & kXMPFiles_CanNotifyProgress) ) progressTracker->BeginWork ( (float) origFileRef->Length() ); XIO::Copy ( origFileRef, tempFileRef, origFileRef->Length(), abortProc, abortArg ); try { @@ -781,6 +1040,8 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) } + if ( progressTracker != 0 && (this->handler->handlerFlags & kXMPFiles_CanNotifyProgress)) progressTracker->WorkComplete(); + } this->ioRef->AbsorbTemp(); @@ -799,9 +1060,9 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) if ( this->handler != 0 ) delete this->handler; } catch ( ... ) { /* Do nothing, throw the outer exception later. */ } - if( this->ioRef ) this->ioRef->DeleteTemp(); + if ( this->ioRef ) this->ioRef->DeleteTemp(); CloseLocalFile ( this ); - this->filePath.clear(); + this->ClearFilePath(); this->handler = 0; this->format = kXMP_UnknownFile; @@ -819,7 +1080,7 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) // Clear the XMPFiles member variables. CloseLocalFile ( this ); - this->filePath.clear(); + this->ClearFilePath(); this->handler = 0; this->format = kXMP_UnknownFile; @@ -829,6 +1090,7 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) if ( this->tempPtr != 0 ) free ( this->tempPtr ); // ! Must have been malloc-ed! this->tempPtr = 0; this->tempUI32 = 0; + XMP_FILES_END1 ( kXMPErrSev_FileFatal ) } // XMPFiles::CloseFile @@ -841,6 +1103,7 @@ XMPFiles::GetFileInfo ( XMP_StringPtr * filePath /* = 0 */, XMP_FileFormat * format /* = 0 */, XMP_OptionBits * handlerFlags /* = 0 */ ) const { + XMP_FILES_START if ( this->handler == 0 ) return false; XMPFileHandler * handler = this->handler; @@ -855,7 +1118,7 @@ XMPFiles::GetFileInfo ( XMP_StringPtr * filePath /* = 0 */, *openFlags = this->openFlags; *format = this->format; *handlerFlags = this->handler->handlerFlags; - + XMP_FILES_END1 ( kXMPErrSev_FileFatal ) return true; } // XMPFiles::GetFileInfo @@ -866,10 +1129,12 @@ void XMPFiles::SetAbortProc ( XMP_AbortProc abortProc, void * abortArg ) { + XMP_FILES_START this->abortProc = abortProc; this->abortArg = abortArg; XMP_Assert ( (abortProc != (XMP_AbortProc)0) || (abortArg != (void*)(unsigned long long)0xDEADBEEFULL) ); // Hack to test the assert callback. + XMP_FILES_END1 ( kXMPErrSev_OperationFatal ) } // XMPFiles::SetAbortProc // ================================================================================================= @@ -906,6 +1171,7 @@ XMPFiles::GetXMP ( SXMPMeta * xmpObj /* = 0 */, XMP_StringLen * xmpPacketLen /* = 0 */, XMP_PacketInfo * packetInfo /* = 0 */ ) { + XMP_FILES_START if ( this->handler == 0 ) XMP_Throw ( "XMPFiles::GetXMP - No open file", kXMPErr_BadObject ); XMP_OptionBits applyTemplateFlags = kXMPTemplate_AddNewProperties | kXMPTemplate_IncludeInternalProperties; @@ -944,7 +1210,7 @@ XMPFiles::GetXMP ( SXMPMeta * xmpObj /* = 0 */, if ( xmpPacketLen != 0 ) *xmpPacketLen = (XMP_StringLen) this->handler->xmpPacket.size(); SetClientPacketInfo ( packetInfo, this->handler->packetInfo, this->handler->xmpPacket, this->handler->needsUpdate ); - + XMP_FILES_END1 ( kXMPErrSev_FileFatal ) return true; } // XMPFiles::GetXMP @@ -1038,7 +1304,9 @@ DoPutXMP ( XMPFiles * thiz, const SXMPMeta & xmpObj, const bool doIt ) void XMPFiles::PutXMP ( const SXMPMeta & xmpObj ) { + XMP_FILES_START (void) DoPutXMP ( this, xmpObj, true ); + XMP_FILES_END1 ( kXMPErrSev_FileFatal ) } // XMPFiles::PutXMP @@ -1048,9 +1316,12 @@ void XMPFiles::PutXMP ( XMP_StringPtr xmpPacket, XMP_StringLen xmpPacketLen /* = kXMP_UseNullTermination */ ) { - SXMPMeta xmpObj ( xmpPacket, xmpPacketLen ); + XMP_FILES_START + SXMPMeta xmpObj; + xmpObj.SetErrorCallback ( ErrorCallbackForXMPMeta, &errorCallback ); + xmpObj.ParseFromBuffer ( xmpPacket, xmpPacketLen ); this->PutXMP ( xmpObj ); - + XMP_FILES_END1 ( kXMPErrSev_FileFatal ) } // XMPFiles::PutXMP // ================================================================================================= @@ -1058,6 +1329,7 @@ XMPFiles::PutXMP ( XMP_StringPtr xmpPacket, bool XMPFiles::CanPutXMP ( const SXMPMeta & xmpObj ) { + XMP_FILES_START if ( this->handler == 0 ) XMP_Throw ( "XMPFiles::CanPutXMP - No open file", kXMPErr_BadObject ); if ( ! (this->openFlags & kXMPFiles_OpenForUpdate) ) return false; @@ -1067,6 +1339,8 @@ XMPFiles::CanPutXMP ( const SXMPMeta & xmpObj ) if ( this->handler->handlerFlags & kXMPFiles_CanExpand ) return true; return DoPutXMP ( this, xmpObj, false ); + XMP_FILES_END1 ( kXMPErrSev_FileFatal ) + return false; } // XMPFiles::CanPutXMP @@ -1076,9 +1350,157 @@ bool XMPFiles::CanPutXMP ( XMP_StringPtr xmpPacket, XMP_StringLen xmpPacketLen /* = kXMP_UseNullTermination */ ) { - SXMPMeta xmpObj ( xmpPacket, xmpPacketLen ); + XMP_FILES_START + SXMPMeta xmpObj; + xmpObj.SetErrorCallback ( ErrorCallbackForXMPMeta, &errorCallback ); + xmpObj.ParseFromBuffer ( xmpPacket, xmpPacketLen ); return this->CanPutXMP ( xmpObj ); + XMP_FILES_END1 ( kXMPErrSev_FileFatal ) + return false; } // XMPFiles::CanPutXMP // ================================================================================================= + +/* class-static */ +void +XMPFiles::SetDefaultProgressCallback ( const XMP_ProgressTracker::CallbackInfo & cbInfo ) +{ + XMP_FILES_STATIC_START + XMP_Assert ( cbInfo.wrapperProc != 0 ); // ! Should be provided by the glue code. + + sProgressDefault = cbInfo; + XMP_FILES_STATIC_END1 ( kXMPErrSev_OperationFatal ) + +} // XMPFiles::SetDefaultProgressCallback + +// ================================================================================================= + +void +XMPFiles::SetProgressCallback ( const XMP_ProgressTracker::CallbackInfo & cbInfo ) +{ + XMP_FILES_START + XMP_Assert ( cbInfo.wrapperProc != 0 ); // ! Should be provided by the glue code. + + if ( (this->handler != 0) && this->UsesClientIO() ) return; // Can't use progress tracking. + + if ( this->progressTracker != 0 ) { + delete this->progressTracker; // ! Delete any existing tracker. + this->progressTracker = 0; + } + + if ( cbInfo.clientProc != 0 ) { + this->progressTracker = new XMP_ProgressTracker ( cbInfo ); + } + XMP_FILES_END1 ( kXMPErrSev_OperationFatal ) + +} // XMPFiles::SetProgressCallback + +// ================================================================================================= +// Error notifications +// =================== + +// ------------------------------------------------------------------------------------------------- +// SetDefaultErrorCallback +// ----------------------- + +/* class-static */ +void XMPFiles::SetDefaultErrorCallback ( XMPFiles_ErrorCallbackWrapper wrapperProc, + XMPFiles_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit ) +{ + XMP_FILES_STATIC_START + XMP_Assert ( wrapperProc != 0 ); // Must always be set by the glue; + + sDefaultErrorCallback.wrapperProc = wrapperProc; + sDefaultErrorCallback.clientProc = clientProc; + sDefaultErrorCallback.context = context; + sDefaultErrorCallback.limit = limit; + XMP_FILES_STATIC_END1 ( kXMPErrSev_OperationFatal ) +} // SetDefaultErrorCallback + +// ------------------------------------------------------------------------------------------------- +// SetErrorCallback +// ---------------- + +void XMPFiles::SetErrorCallback ( XMPFiles_ErrorCallbackWrapper wrapperProc, + XMPFiles_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit ) +{ + XMP_FILES_START + XMP_Assert ( wrapperProc != 0 ); // Must always be set by the glue; + + this->errorCallback.Clear(); + this->errorCallback.wrapperProc = wrapperProc; + this->errorCallback.clientProc = clientProc; + this->errorCallback.context = context; + this->errorCallback.limit = limit; + XMP_FILES_END1 ( kXMPErrSev_OperationFatal ) +} // SetErrorCallback + +// ------------------------------------------------------------------------------------------------- +// ResetErrorCallbackLimit +// ----------------------- + +void XMPFiles::ResetErrorCallbackLimit ( XMP_Uns32 limit ) { + XMP_FILES_START + this->errorCallback.limit = limit; + this->errorCallback.notifications = 0; + this->errorCallback.topSeverity = kXMPErrSev_Recoverable; + XMP_FILES_END1 ( kXMPErrSev_OperationFatal ) +} // ResetErrorCallbackLimit + +// ------------------------------------------------------------------------------------------------- +// ErrorCallbackInfo::CanNotify +// ------------------------------- +// +// This is const just to be usable from const XMPMeta functions. + +bool + XMPFiles::ErrorCallbackInfo::CanNotify() const +{ + XMP_Assert ( (this->clientProc == 0) || (this->wrapperProc != 0) ); + return ( this->clientProc != 0 ); +} + +// ------------------------------------------------------------------------------------------------- +// ErrorCallbackInfo::ClientCallbackWrapper +// ------------------------------- +// +// This is const just to be usable from const XMPMeta functions. + +bool + XMPFiles::ErrorCallbackInfo::ClientCallbackWrapper ( XMP_StringPtr filePath, + XMP_ErrorSeverity severity, + XMP_Int32 cause, + XMP_StringPtr messsage ) const +{ + + XMP_StringPtr filePathPtr = filePath; + if ( filePathPtr == 0 ) { + filePathPtr = this->filePath.c_str(); + } + + XMP_Bool retValue = (*this->wrapperProc) ( this->clientProc, this->context, filePathPtr, severity, cause, messsage ); + return ConvertXMP_BoolToBool(retValue); +} + +// ------------------------------------------------------------------------------------------------- +// ErrorCallbackForXMPMeta +// ------------------------------- + +bool + ErrorCallbackForXMPMeta ( void * context, XMP_ErrorSeverity severity, + XMP_Int32 cause, + XMP_StringPtr message) +{ + //typecast context to GenericErrorCallback + GenericErrorCallback * callback = ( GenericErrorCallback * ) context; + XMP_Error error ( cause, message ); + callback->NotifyClient ( severity, error ); + return !kXMP_Bool_False; +} + +// ================================================================================================= diff --git a/XMPFiles/source/XMPFiles.hpp b/XMPFiles/source/XMPFiles.hpp index f4f2e96..70ab020 100644 --- a/XMPFiles/source/XMPFiles.hpp +++ b/XMPFiles/source/XMPFiles.hpp @@ -18,6 +18,8 @@ #include "public/include/XMP_IO.hpp" +#include "source/XMP_ProgressTracker.hpp" + class XMPFileHandler; namespace Common{ struct XMPFileHandlerInfo; } @@ -57,10 +59,7 @@ namespace Common{ struct XMPFileHandlerInfo; } // 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. // @@ -137,7 +136,7 @@ namespace Common{ struct XMPFileHandlerInfo; } // 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 +// The handler is free to use its best judgment 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. // @@ -161,92 +160,133 @@ namespace Common{ struct XMPFileHandlerInfo; } class XMPFiles { public: + static bool Initialize(XMP_OptionBits options, const char* pluginFolder, const char* plugins = NULL); + static void Terminate(); - static void GetVersionInfo ( XMP_VersionInfo * info ); + 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 bool GetFileModDate( + XMP_StringPtr filePath, + XMP_DateTime * modDate, + XMP_FileFormat * format = 0, + XMP_OptionBits options = 0); - 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 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 ); + static bool GetAssociatedResources ( + XMP_StringPtr filePath, + std::vector<std::string> * resourceList, + XMP_FileFormat format = kXMP_UnknownFile , + XMP_OptionBits options = 0 ); - bool OpenFile ( XMP_StringPtr filePath, - XMP_FileFormat format = kXMP_UnknownFile, - XMP_OptionBits openFlags = 0 ); + static bool IsMetadataWritable ( + XMP_StringPtr filePath, + XMP_Bool * writable, + XMP_FileFormat format = kXMP_UnknownFile , + XMP_OptionBits options = 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 + static void SetDefaultProgressCallback(const XMP_ProgressTracker::CallbackInfo & cbInfo); + static void SetDefaultErrorCallback(XMPFiles_ErrorCallbackWrapper wrapperProc, + XMPFiles_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit); - bool OpenFile( const Common::XMPFileHandlerInfo& hdlInfo, - XMP_StringPtr filePath, - XMP_OptionBits openFlags = 0 ); + XMPFiles(); + virtual ~XMPFiles(); + + bool OpenFile(XMP_StringPtr filePath, XMP_FileFormat format = kXMP_UnknownFile, XMP_OptionBits openFlags = 0); + 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 ); + bool OpenFile(XMP_IO * clientIO, XMP_FileFormat format = kXMP_UnknownFile, XMP_OptionBits openFlags = 0); + bool OpenFile(const Common::XMPFileHandlerInfo & hdlInfo, XMP_IO * clientIO, XMP_OptionBits openFlags = 0); #endif - void CloseFile ( XMP_OptionBits closeFlags = 0 ); + 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; + 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); - 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); - void PutXMP ( const SXMPMeta & xmpObj ); + bool CanPutXMP(const SXMPMeta & xmpObj); + bool CanPutXMP(XMP_StringPtr xmpPacket, XMP_StringLen xmpPacketLen = kXMP_UseNullTermination); - void PutXMP ( XMP_StringPtr xmpPacket, - XMP_StringLen xmpPacketLen = kXMP_UseNullTermination ); + void SetAbortProc(XMP_AbortProc abortProc, void * abortArg); - bool CanPutXMP ( const SXMPMeta & xmpObj ); + void SetProgressCallback(const XMP_ProgressTracker::CallbackInfo & cbInfo); - bool CanPutXMP ( XMP_StringPtr xmpPacket, - XMP_StringLen xmpPacketLen = kXMP_UseNullTermination ); + void SetErrorCallback( + XMPFiles_ErrorCallbackWrapper wrapperProc, + XMPFiles_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit); + void ResetErrorCallbackLimit(XMP_Uns32 limit); - inline bool UsesClientIO() { return this->filePath.empty(); }; - inline bool UsesLocalIO() { return ( ! this->UsesClientIO() ); }; + class ErrorCallbackInfo : public GenericErrorCallback { + public: + XMPFiles_ErrorCallbackWrapper wrapperProc; + XMPFiles_ErrorCallbackProc clientProc; + void * context; + std::string filePath; - // Leave this data public so file handlers can see it. + ErrorCallbackInfo() + : wrapperProc(0) + , clientProc(0) + , context(0) {}; - XMP_Int32 clientRefs; // ! Must be signed to allow decrement from zero. - XMP_ReadWriteLock lock; + void Clear() { + this->wrapperProc = 0; this->clientProc = 0; this->context = 0; + GenericErrorCallback::Clear(); + }; - 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. + bool CanNotify() const; - void * tempPtr; // For use between the CheckProc and handler creation. - XMP_Uns32 tempUI32; + bool ClientCallbackWrapper( + XMP_StringPtr filePath, + XMP_ErrorSeverity severity, + XMP_Int32 cause, + XMP_StringPtr messsage) const; + }; - XMP_AbortProc abortProc; - void * abortArg; + inline bool UsesClientIO() { return this->filePath.empty(); }; + inline bool UsesLocalIO() { return ( ! this->UsesClientIO() ); }; + inline void SetFilePath(XMP_StringPtr _filePath) { filePath = _filePath; errorCallback.filePath = _filePath; } + inline void ClearFilePath() { filePath.clear(); errorCallback.filePath.clear(); } + inline const std::string& GetFilePath() { return filePath; } -}; // XMPFiles + // 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. + 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; + XMP_ProgressTracker * progressTracker; + ErrorCallbackInfo errorCallback; + +private: + std::string filePath; // Empty for client-managed I/O. +}; + +bool ErrorCallbackForXMPMeta(void * context, XMP_ErrorSeverity severity, XMP_Int32 cause, XMP_StringPtr message); #endif /* __XMPFiles_hpp__ */ diff --git a/XMPFiles/source/XMPFiles_Impl.cpp b/XMPFiles/source/XMPFiles_Impl.cpp index 92b2ea3..471c48d 100644 --- a/XMPFiles/source/XMPFiles_Impl.cpp +++ b/XMPFiles/source/XMPFiles_Impl.cpp @@ -44,6 +44,10 @@ using namespace std; /// // ================================================================================================= +#if ! XMP_StaticBuild + #include "public/include/XMP.incl_cpp" +#endif + #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) @@ -52,6 +56,15 @@ using namespace std; bool ignoreLocalText = false; XMP_FileFormat voidFileFormat = 0; // Used as sink for unwanted output parameters. + +#if ! XMP_StaticBuild + XMP_PacketInfo voidPacketInfo; + void * voidVoidPtr = 0; + XMP_StringPtr voidStringPtr = 0; + XMP_StringLen voidStringLen = 0; + XMP_OptionBits voidOptionBits = 0; +#endif + // ================================================================================================= // Add all known mappings, multiple mappings (tif, tiff) are OK. @@ -89,6 +102,7 @@ const FileExtMapping kFileExtMap[] = { "wma", kXMP_WMAVFile }, { "wmv", kXMP_WMAVFile }, { "mxf", kXMP_MXFFile }, + { "r3d", kXMP_REDFile }, { "mpg", kXMP_MPEGFile }, { "mpeg", kXMP_MPEGFile }, @@ -165,7 +179,6 @@ const char * kKnownRejectedFiles[] = // UCF subformats "air", // Others - "r3d", 0 }; // ! Keep a 0 sentinel at the end. // ================================================================================================= @@ -369,15 +382,90 @@ bool XMPFileHandler::GetFileModDate ( XMP_DateTime * modDate ) XMP_Throw ( "Base implementation of GetFileModDate only for typical embedding handlers", kXMPErr_InternalFailure ); } - if ( this->parent->filePath.empty() ) { + if ( this->parent->GetFilePath().empty() ) { XMP_Throw ( "GetFileModDate cannot be used with client-provided I/O", kXMPErr_InternalFailure ); } - return Host_IO::GetModifyDate ( this->parent->filePath.c_str(), modDate ); + return Host_IO::GetModifyDate ( this->parent->GetFilePath().c_str(), modDate ); } // XMPFileHandler::GetFileModDate // ================================================================================================= +// XMPFileHandler::FillMetadataFiles +// ============================== +// +// The base implementation is only for files having embedded metadata for which the same file will +// be returned. + +void XMPFileHandler::FillMetadataFiles ( std::vector<std::string> * metadataFiles ) +{ + XMP_OptionBits flags = this->handlerFlags; + if ( (flags & kXMPFiles_HandlerOwnsFile) || + (flags & kXMPFiles_UsesSidecarXMP) || + (flags & kXMPFiles_FolderBasedFormat) ) { + XMP_Throw ( "Base implementation of FillMetadataFiles only for typical embedding handlers", kXMPErr_InternalFailure ); + } + + if ( this->parent->GetFilePath().empty() ) { + XMP_Throw ( "FillMetadataFiles cannot be used with client-provided I/O", kXMPErr_InternalFailure ); + } + + metadataFiles->push_back ( std::string ( this->parent->GetFilePath().c_str() ) ); +} // XMPFileHandler::FillMetadataFiles + +// ================================================================================================= +// XMPFileHandler::FillAssociatedResources +// ======================================= +// +// The base implementation is only for files having embedded metadata for which the same file will +// be returned as the associated resource. +// + +void XMPFileHandler::FillAssociatedResources ( std::vector<std::string> * metadataFiles ) +{ + XMP_OptionBits flags = this->handlerFlags; + if ( (flags & kXMPFiles_HandlerOwnsFile) || + (flags & kXMPFiles_UsesSidecarXMP) || + (flags & kXMPFiles_FolderBasedFormat) ) { + XMP_Throw ( "GetAssociatedResources is not implemented for this file format", kXMPErr_InternalFailure ); + } + + if ( this->parent->GetFilePath().empty() ) { + XMP_Throw ( "GetAssociatedResources cannot be used with client-provided I/O", kXMPErr_InternalFailure ); + } + + metadataFiles->push_back ( std::string ( this->parent->GetFilePath().c_str() ) ); +} // XMPFileHandler::FillAssociatedResources + +// ================================================================================================= +// XMPFileHandler::IsMetadataWritable +// ======================================= +// +// The base implementation is only for files having embedded metadata and it checks whether that +// file is writable or no.Returns true if the file is writable. +// +bool XMPFileHandler::IsMetadataWritable ( ) +{ + XMP_OptionBits flags = this->handlerFlags; + if ( (flags & kXMPFiles_HandlerOwnsFile) || + (flags & kXMPFiles_UsesSidecarXMP) || + (flags & kXMPFiles_FolderBasedFormat) ) { + XMP_Throw ( "IsMetadataWritable is not implemented for this file format", kXMPErr_InternalFailure ); + } + + if ( this->parent->GetFilePath().empty() ) { + XMP_Throw ( "IsMetadataWritable cannot be used with client-provided I/O", kXMPErr_InternalFailure ); + } + + try { + return Host_IO::Writable( this->parent->GetFilePath().c_str() ); + } catch ( ... ) { + + } + return false; +} + +// ================================================================================================= // XMPFileHandler::ProcessXMP // ========================== // diff --git a/XMPFiles/source/XMPFiles_Impl.hpp b/XMPFiles/source/XMPFiles_Impl.hpp index 689aa43..6e21c23 100644 --- a/XMPFiles/source/XMPFiles_Impl.hpp +++ b/XMPFiles/source/XMPFiles_Impl.hpp @@ -75,6 +75,14 @@ extern bool ignoreLocalText; #define EnablePacketScanning 1 #endif +#ifndef EnablePluginManager + #if XMP_iOSBuild + #define EnablePluginManager 0 + #else + #define EnablePluginManager 1 + #endif +#endif + extern XMP_Int32 sXMPFilesInitCount; #ifndef GatherPerformanceData @@ -157,7 +165,15 @@ extern const FileExtMapping kFileExtMap[]; extern const char * kKnownScannedFiles[]; extern const char * kKnownRejectedFiles[]; -typedef std::map < const char *, const char * > ID3GenreMap; +class CharStarLess { // Comparison object for the genre code maps. +public: + bool operator() ( const char * left, const char * right ) const { + int order = strcmp ( left, right ); + return order < 0; + } +}; + +typedef std::map < const char *, const char *, CharStarLess > ID3GenreMap; extern ID3GenreMap* kMapID3GenreCodeToName; // Storage defined in ID3_Support.cpp. extern ID3GenreMap* kMapID3GenreNameToCode; @@ -266,11 +282,17 @@ public: containsXMP(false), processedXMP(false), needsUpdate(false) XMPFileHandler() : parent(0), DefaultCTorPresets {}; - XMPFileHandler (XMPFiles * _parent) : parent(_parent), DefaultCTorPresets {}; + XMPFileHandler (XMPFiles * _parent) : parent(_parent), DefaultCTorPresets + { + xmpObj.SetErrorCallback(ErrorCallbackForXMPMeta, &parent->errorCallback); + }; 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 FillMetadataFiles ( std::vector<std::string> * metadataFiles ); + virtual void FillAssociatedResources ( std::vector<std::string> * resourceList ); + virtual bool IsMetadataWritable ( ); virtual void CacheFileData() = 0; virtual void ProcessXMP(); // The default implementation just parses the XMP. |