diff options
Diffstat (limited to 'source/XMPFiles')
93 files changed, 13823 insertions, 9348 deletions
diff --git a/source/XMPFiles/FileHandlers/ASF_Handler.cpp b/source/XMPFiles/FileHandlers/ASF_Handler.cpp index 5726533..ac6aad7 100644 --- a/source/XMPFiles/FileHandlers/ASF_Handler.cpp +++ b/source/XMPFiles/FileHandlers/ASF_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -114,17 +114,6 @@ void ASF_MetaHandler::CacheFileData() } // ASF_MetaHandler::CacheFileData // ================================================================================================= -// ASF_MetaHandler::ProcessTNail -// ============================== - -void ASF_MetaHandler::ProcessTNail() -{ - - XMP_Throw ( "ASF_MetaHandler::ProcessTNail isn't implemented yet", kXMPErr_Unimplemented ); - -} // ASF_MetaHandler::ProcessTNail - -// ================================================================================================= // ASF_MetaHandler::ProcessXMP // ============================ // @@ -341,6 +330,7 @@ bool ASF_MetaHandler::SafeWriteFile () ret = true; } catch ( ... ) { LFA_Close ( updateRef ); + LFA_Delete ( updatePath.c_str() ); this->parent->filePath = origPath; this->parent->fileRef = origRef; throw; diff --git a/source/XMPFiles/FileHandlers/ASF_Handler.hpp b/source/XMPFiles/FileHandlers/ASF_Handler.hpp index 9f54b15..e5fec1c 100644 --- a/source/XMPFiles/FileHandlers/ASF_Handler.hpp +++ b/source/XMPFiles/FileHandlers/ASF_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -26,9 +26,9 @@ extern XMPFileHandler* ASF_MetaHandlerCTor ( XMPFiles* parent ); extern bool ASF_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef fileRef, - XMPFiles* parent ); + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ); static const XMP_OptionBits kASF_HandlerFlags = ( kXMPFiles_CanInjectXMP | kXMPFiles_CanExpand | @@ -36,15 +36,13 @@ static const XMP_OptionBits kASF_HandlerFlags = ( kXMPFiles_CanInjectXMP | kXMPFiles_CanReconcile | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | - kXMPFiles_NeedsReadOnlyPacket - ); + kXMPFiles_NeedsReadOnlyPacket ); class ASF_MetaHandler : public XMPFileHandler { public: void CacheFileData(); - void ProcessTNail(); void ProcessXMP(); void UpdateFile ( bool doSafeUpdate ); diff --git a/source/XMPFiles/FileHandlers/AVCHD_Handler.cpp b/source/XMPFiles/FileHandlers/AVCHD_Handler.cpp index 63b8f29..f842a73 100644 --- a/source/XMPFiles/FileHandlers/AVCHD_Handler.cpp +++ b/source/XMPFiles/FileHandlers/AVCHD_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -10,9 +10,16 @@ #include "AVCHD_Handler.hpp" #include "MD5.h" +#include "UnicodeConversions.hpp" using namespace std; +// AVCHD maker ID values. Panasonic has confirmed their Maker ID with us, the others come from examining +// sample data files. +#define kMakerIDPanasonic 0x103 +#define kMakerIDSony 0x108 +#define kMakerIDCanon 0x1011 + // ================================================================================================= /// \file AVCHD_Handler.cpp /// \brief Folder format handler for AVCHD. @@ -85,6 +92,319 @@ struct AVCHD_blkProgramInfo }; +// AVCHD Format, Panasonic proprietary PRO_PlayListMark block + +struct AVCCAM_blkProPlayListMark +{ + XMP_Uns8 mPresent; + XMP_Uns8 mProTagID; + XMP_Uns8 mFillItem1; + XMP_Uns16 mLength; + XMP_Uns8 mMarkType; + + // Entry mark + struct + { + XMP_Uns8 mGlobalClipID[32]; + XMP_Uns8 mStartTimeCode[4]; + XMP_Uns8 mStreamTimecodeInfo; + XMP_Uns8 mStartBinaryGroup[4]; + XMP_Uns8 mLastUpdateTimeZone; + XMP_Uns8 mLastUpdateDate[7]; + XMP_Uns16 mFillItem; + } mEntryMark; + + // Shot Mark + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mShotMark; + XMP_Uns8 mFillItem[3]; + } mShotMark; + + // Access + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mCreatorCharacterSet; + XMP_Uns8 mCreatorLength; + XMP_Uns8 mCreator[32]; + XMP_Uns8 mLastUpdatePersonCharacterSet; + XMP_Uns8 mLastUpdatePersonLength; + XMP_Uns8 mLastUpdatePerson[32]; + } mAccess; + + // Device + struct + { + XMP_Uns8 mPresent; + XMP_Uns16 mMakerID; + XMP_Uns16 mMakerModelCode; + XMP_Uns8 mSerialNoCharacterCode; + XMP_Uns8 mSerialNoLength; + XMP_Uns8 mSerialNo[24]; + XMP_Uns16 mFillItem; + } mDevice; + + // Shoot + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mShooterCharacterSet; + XMP_Uns8 mShooterLength; + XMP_Uns8 mShooter[32]; + XMP_Uns8 mStartDateTimeZone; + XMP_Uns8 mStartDate[7]; + XMP_Uns8 mEndDateTimeZone; + XMP_Uns8 mEndDate[7]; + XMP_Uns16 mFillItem; + } mShoot; + + // Location + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mSource; + XMP_Uns32 mGPSLatitudeRef; + XMP_Uns32 mGPSLatitude1; + XMP_Uns32 mGPSLatitude2; + XMP_Uns32 mGPSLatitude3; + XMP_Uns32 mGPSLongitudeRef; + XMP_Uns32 mGPSLongitude1; + XMP_Uns32 mGPSLongitude2; + XMP_Uns32 mGPSLongitude3; + XMP_Uns32 mGPSAltitudeRef; + XMP_Uns32 mGPSAltitude; + XMP_Uns8 mPlaceNameCharacterSet; + XMP_Uns8 mPlaceNameLength; + XMP_Uns8 mPlaceName[64]; + XMP_Uns8 mFillItem; + } mLocation; +}; + +// AVCHD Format, Panasonic proprietary extension data (AVCCAM) + +struct AVCCAM_Pro_PlayListInfo +{ + XMP_Uns8 mPresent; + XMP_Uns8 mTagID; + XMP_Uns8 mTagVersion; + XMP_Uns16 mFillItem1; + XMP_Uns32 mLength; + XMP_Uns16 mNumberOfPlayListMarks; + XMP_Uns16 mFillItem2; + + // Although a playlist may contain multiple marks, we only store the one that corresponds to + // the clip/shot of interest. + AVCCAM_blkProPlayListMark mPlayListMark; +}; + +// AVCHD Format, Panasonic proprietary extension data (AVCCAM) + +struct AVCHD_blkPanasonicPrivateData +{ + XMP_Uns8 mPresent; + XMP_Uns16 mNumberOfData; + XMP_Uns16 mReserved; + + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mTagID; + XMP_Uns8 mTagVersion; + XMP_Uns16 mTagLength; + XMP_Uns8 mProfessionalMetaID[16]; + } mProMetaIDBlock; + + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mTagID; + XMP_Uns8 mTagVersion; + XMP_Uns16 mTagLength; + XMP_Uns8 mGlobalClipID[32]; + XMP_Uns8 mStartTimecode[4]; + XMP_Uns32 mStartBinaryGroup; + } mProClipIDBlock; + + AVCCAM_Pro_PlayListInfo mProPlaylistInfoBlock; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.2.4.2. plus Panasonic extensions + +struct AVCHD_blkMakersPrivateData +{ + XMP_Uns8 mPresent; + XMP_Uns32 mLength; + XMP_Uns32 mDataBlockStartAddress; + XMP_Uns8 mReserved[3]; + XMP_Uns8 mNumberOfMakerEntries; + XMP_Uns16 mMakerID; + XMP_Uns16 mMakerModelCode; + AVCHD_blkPanasonicPrivateData mPanasonicPrivateData; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.4.2.1 + +struct AVCHD_blkClipInfoExt +{ + XMP_Uns32 mLength; + XMP_Uns16 mMakerID; + XMP_Uns16 mMakerModelCode; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.4.1.2 + +struct AVCHD_blkClipExtensionData +{ + XMP_Uns8 mPresent; + XMP_Uns8 mTypeIndicator[4]; + XMP_Uns8 mReserved1[4]; + XMP_Uns32 mProgramInfoExtStartAddress; + XMP_Uns32 mMakersPrivateDataStartAddress; + + AVCHD_blkClipInfoExt mClipInfoExt; + AVCHD_blkMakersPrivateData mMakersPrivateData; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.3.3.1 -- although each playlist +// may contain a list of these, we only record the one that matches our target shot/clip. + +struct AVCHD_blkPlayListMarkExt +{ + XMP_Uns32 mLength; + XMP_Uns16 mNumberOfPlaylistMarks; + bool mPresent; + XMP_Uns16 mMakerID; + XMP_Uns16 mMakerModelCode; + XMP_Uns8 mReserved1[3]; + XMP_Uns8 mFlags; // bit 0: MarkWriteProtectFlag, bits 1-2: pulldown + XMP_Uns16 mRefToMarkThumbnailIndex; + XMP_Uns8 mBlkTimezone; + XMP_Uns8 mRecordDataAndTime[7]; + XMP_Uns8 mMarkCharacterSet; + XMP_Uns8 mMarkNameLength; + XMP_Uns8 mMarkName[24]; + XMP_Uns8 mMakersInformation[16]; + XMP_Uns8 mBlkTimecode[4]; + XMP_Uns16 mReserved2; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.3.2.1 + +struct AVCHD_blkPlaylistMeta +{ + XMP_Uns32 mLength; + XMP_Uns16 mMakerID; + XMP_Uns16 mMakerModelCode; + XMP_Uns32 mReserved1; + XMP_Uns16 mRefToMenuThumbnailIndex; + XMP_Uns8 mBlkTimezone; + XMP_Uns8 mRecordDataAndTime[7]; + XMP_Uns8 mReserved2; + XMP_Uns8 mPlaylistCharacterSet; + XMP_Uns8 mPlaylistNameLength; + XMP_Uns8 mPlaylistName[255]; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.3.1.2 + +struct AVCHD_blkPlayListExtensionData +{ + XMP_Uns8 mPresent; + char mTypeIndicator[4]; + XMP_Uns8 mReserved[4]; + XMP_Uns32 mPlayListMarkExtStartAddress; + XMP_Uns32 mMakersPrivateDataStartAddress; + + AVCHD_blkPlaylistMeta mPlaylistMeta; + AVCHD_blkPlayListMarkExt mPlaylistMarkExt; + AVCHD_blkMakersPrivateData mMakersPrivateData; +}; + +// AVCHD Format. Book 1: Playback System Basic Specifications V 1.01. p. 38 +struct AVCHD_blkExtensionData +{ + XMP_Uns32 mLength; + XMP_Uns32 mDataBlockStartAddress; + XMP_Uns8 mReserved[3]; + XMP_Uns8 mNumberOfDataEntries; + + struct AVCHD_blkExtDataEntry + { + XMP_Uns16 mExtDataType; + XMP_Uns16 mExtDataVersion; + XMP_Uns32 mExtDataStartAddress; + XMP_Uns32 mExtDataLength; + } mExtDataEntry; +}; + +// Simple container for the various AVCHD legacy metadata structures we care about for an AVCHD clip + +struct AVCHD_LegacyMetadata +{ + AVCHD_blkProgramInfo mProgramInfo; + AVCHD_blkClipExtensionData mClipExtensionData; + AVCHD_blkPlayListExtensionData mPlaylistExtensionData; +}; + +// ================================================================================================= +// MakeLeafPath +// ============ + +static bool MakeLeafPath ( std::string * path, XMP_StringPtr root, XMP_StringPtr group, + XMP_StringPtr clip, XMP_StringPtr suffix, bool checkFile = false ) +{ + size_t partialLen; + + *path = root; + *path += kDirChar; + *path += "BDMV"; + *path += kDirChar; + *path += group; + *path += kDirChar; + *path += clip; + partialLen = path->size(); + *path += suffix; + + if ( ! checkFile ) return true; + if ( GetFileMode ( path->c_str() ) == kFMode_IsFile ) return true; + + // Convert the suffix to uppercase and try again. Even on Mac/Win, in case a remote file system is sensitive. + for ( char* chPtr = ((char*)path->c_str() + partialLen); *chPtr != 0; ++chPtr ) { + if ( (0x61 <= *chPtr) && (*chPtr <= 0x7A) ) *chPtr -= 0x20; + } + if ( GetFileMode ( path->c_str() ) == kFMode_IsFile ) return true; + + if ( XMP_LitMatch ( suffix, ".clpi" ) ) { // Special case of ".cpi" for the clip file. + + path->erase ( partialLen ); + *path += ".cpi"; + if ( GetFileMode ( path->c_str() ) == kFMode_IsFile ) return true; + + path->erase ( partialLen ); + *path += ".CPI"; + if ( GetFileMode ( path->c_str() ) == kFMode_IsFile ) return true; + + } else if ( XMP_LitMatch ( suffix, ".mpls" ) ) { // Special case of ".mpl" for the playlist file. + + path->erase ( partialLen ); + *path += ".mpl"; + if ( GetFileMode ( path->c_str() ) == kFMode_IsFile ) return true; + + path->erase ( partialLen ); + *path += ".MPL"; + if ( GetFileMode ( path->c_str() ) == kFMode_IsFile ) return true; + + } + + // Still not found, revert to the original suffix. + path->erase ( partialLen ); + *path += suffix; + return false; + +} // MakeLeafPath + // ================================================================================================= // AVCHD_CheckFormat // ================= @@ -138,36 +458,29 @@ bool AVCHD_CheckFormat ( XMP_FileFormat format, if ( GetChildMode ( bdmvPath, "STREAM" ) != kFMode_IsFolder ) return false; if ( (GetChildMode ( bdmvPath, "index.bdmv" ) != kFMode_IsFile) && - (GetChildMode ( bdmvPath, "index.bdm" ) != kFMode_IsFile) ) return false; + (GetChildMode ( bdmvPath, "index.bdm" ) != kFMode_IsFile) && + (GetChildMode ( bdmvPath, "INDEX.BDMV" ) != kFMode_IsFile) && // Some usage is all caps. + (GetChildMode ( bdmvPath, "INDEX.BDM" ) != kFMode_IsFile) ) return false; if ( (GetChildMode ( bdmvPath, "MovieObject.bdmv" ) != kFMode_IsFile) && - (GetChildMode ( bdmvPath, "MovieObj.bdm" ) != kFMode_IsFile) ) return false; + (GetChildMode ( bdmvPath, "MovieObj.bdm" ) != kFMode_IsFile) && + (GetChildMode ( bdmvPath, "MOVIEOBJECT.BDMV" ) != kFMode_IsFile) && // Some usage is all caps. + (GetChildMode ( bdmvPath, "MOVIEOBJ.BDM" ) != kFMode_IsFile) ) return false; // Make sure the .clpi file exists. - std::string tempPath ( bdmvPath ); - tempPath += kDirChar; - tempPath += "CLIPINF"; - tempPath += kDirChar; - tempPath += leafName; - tempPath += ".clpi"; - const bool foundClpi = ( GetFileMode ( tempPath.c_str() ) == kFMode_IsFile ); - - // No .clpi -- check for .cpi instead. - if ( ! foundClpi ) { - tempPath.erase ( tempPath.size() - 5 ); // Remove the ".clpi" part. - tempPath += ".cpi"; - if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false; - } + std::string tempPath; + bool foundClpi = MakeLeafPath ( &tempPath, rootPath.c_str(), "CLIPINF", leafName.c_str(), ".clpi", true /* checkFile */ ); + if ( ! foundClpi ) return false; // And now save the pseudo path for the handler object. tempPath = rootPath; tempPath += kDirChar; tempPath += leafName; size_t pathLen = tempPath.size() + 1; // Include a terminating nul. - parent->handlerTemp = malloc ( pathLen ); - if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for AVCHD clip info", kXMPErr_NoMemory ); - memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen ); + parent->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for AVCHD clip info", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); return true; @@ -175,130 +488,1114 @@ bool AVCHD_CheckFormat ( XMP_FileFormat format, // ================================================================================================= // ReadAVCHDProgramInfo +// ============ + +static bool ReadAVCHDProgramInfo ( LFA_FileRef& cpiFileRef, AVCHD_blkProgramInfo& avchdProgramInfo ) +{ + avchdProgramInfo.mLength = LFA_ReadUns32_BE ( cpiFileRef ); + LFA_Read ( cpiFileRef, avchdProgramInfo.mReserved1, 2 ); + avchdProgramInfo.mSPNProgramSequenceStart = LFA_ReadUns32_BE ( cpiFileRef ); + avchdProgramInfo.mProgramMapPID = LFA_ReadUns16_BE ( cpiFileRef ); + LFA_Read ( cpiFileRef, &avchdProgramInfo.mNumberOfStreamsInPS, 1 ); + LFA_Read ( cpiFileRef, &avchdProgramInfo.mReserved2, 1 ); + + XMP_Uns16 streamPID = 0; + for ( int i=0; i<avchdProgramInfo.mNumberOfStreamsInPS; ++i ) { + + XMP_Uns8 length = 0; + XMP_Uns8 streamCodingType = 0; + + streamPID = LFA_ReadUns16_BE ( cpiFileRef ); + LFA_Read ( cpiFileRef, &length, 1 ); + + XMP_Int64 pos = LFA_Tell ( cpiFileRef ); + + LFA_Read ( cpiFileRef, &streamCodingType, 1 ); + + switch ( streamCodingType ) { + + case 0x1B : // Video stream case. + { + XMP_Uns8 videoFormatAndFrameRate; + LFA_Read ( cpiFileRef, &videoFormatAndFrameRate, 1 ); + avchdProgramInfo.mVideoStream.mVideoFormat = videoFormatAndFrameRate >> 4; // hi 4 bits + avchdProgramInfo.mVideoStream.mFrameRate = videoFormatAndFrameRate & 0x0f; // lo 4 bits + + XMP_Uns8 aspectRatioAndReserved = 0; + LFA_Read ( cpiFileRef, &aspectRatioAndReserved, 1 ); + avchdProgramInfo.mVideoStream.mAspectRatio = aspectRatioAndReserved >> 4; // hi 4 bits + + XMP_Uns8 ccFlag = 0; + LFA_Read ( cpiFileRef, &ccFlag, 1 ); + avchdProgramInfo.mVideoStream.mCCFlag = ccFlag; + + avchdProgramInfo.mVideoStream.mPresent = 1; + } + break; + + case 0x80 : // Fall through. + case 0x81 : // Audio stream case. + { + XMP_Uns8 audioPresentationTypeAndFrequency = 0; + LFA_Read ( cpiFileRef, &audioPresentationTypeAndFrequency, 1 ); + + avchdProgramInfo.mAudioStream.mAudioPresentationType = audioPresentationTypeAndFrequency >> 4; // hi 4 bits + avchdProgramInfo.mAudioStream.mSamplingFrequency = audioPresentationTypeAndFrequency & 0x0f; // lo 4 bits + + LFA_Read ( cpiFileRef, avchdProgramInfo.mAudioStream.mAudioLanguageCode, 3 ); + avchdProgramInfo.mAudioStream.mAudioLanguageCode[3] = 0; + + avchdProgramInfo.mAudioStream.mPresent = 1; + } + break; + + case 0x90 : // Overlay bitmap stream case. + LFA_Read ( cpiFileRef, &avchdProgramInfo.mOverlayBitmapStream.mOBLanguageCode, 3 ); + avchdProgramInfo.mOverlayBitmapStream.mOBLanguageCode[3] = 0; + avchdProgramInfo.mOverlayBitmapStream.mPresent = 1; + break; + + case 0x91 : // Menu bitmap stream. + LFA_Read ( cpiFileRef, &avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode, 3 ); + avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode[3] = 0; + avchdProgramInfo.mMenuBitmapStream.mPresent = 1; + break; + + default : + break; + + } + + LFA_Seek ( cpiFileRef, pos + length, SEEK_SET ); + + } + + return true; +} + +// ================================================================================================= +// ReadAVCHDExtensionData +// ============ + +static bool ReadAVCHDExtensionData ( LFA_FileRef& cpiFileRef, AVCHD_blkExtensionData& extensionDataHeader ) +{ + extensionDataHeader.mLength = LFA_ReadUns32_BE ( cpiFileRef ); + + if ( extensionDataHeader.mLength == 0 ) { + // Nothing to read + return true; + } + + extensionDataHeader.mDataBlockStartAddress = LFA_ReadUns32_BE ( cpiFileRef ); + LFA_Read ( cpiFileRef, extensionDataHeader.mReserved, 3 ); + LFA_Read ( cpiFileRef, &extensionDataHeader.mNumberOfDataEntries, 1 ); + + if ( extensionDataHeader.mNumberOfDataEntries != 1 ) { + // According to AVCHD Format. Book1. v. 1.01. p 38, "This field shall be set to 1 in this format." + return false; + } + + extensionDataHeader.mExtDataEntry.mExtDataType = LFA_ReadUns16_BE ( cpiFileRef ); + extensionDataHeader.mExtDataEntry.mExtDataVersion = LFA_ReadUns16_BE ( cpiFileRef ); + extensionDataHeader.mExtDataEntry.mExtDataStartAddress = LFA_ReadUns32_BE ( cpiFileRef ); + extensionDataHeader.mExtDataEntry.mExtDataLength = LFA_ReadUns32_BE ( cpiFileRef ); + + if ( extensionDataHeader.mExtDataEntry.mExtDataType != 0x1000 ) { + // According to AVCHD Format. Book1. v. 1.01. p 38, "If the metadata is for an AVCHD application, + // this value shall be set to 'Ox1OOO'." + return false; + } + + return true; +} + +// ================================================================================================= +// ReadAVCCAMProMetaID-- read Panasonic's proprietary PRO_MetaID block +// ============ + +static bool ReadAVCCAMProMetaID ( LFA_FileRef& cpiFileRef, XMP_Uns8 tagID, AVCHD_blkPanasonicPrivateData& extensionDataHeader ) +{ + extensionDataHeader.mPresent = 1; + extensionDataHeader.mProMetaIDBlock.mPresent = 1; + extensionDataHeader.mProMetaIDBlock.mTagID = tagID; + LFA_Read ( cpiFileRef, &extensionDataHeader.mProMetaIDBlock.mTagVersion, 1); + extensionDataHeader.mProMetaIDBlock.mTagLength = LFA_ReadUns16_BE ( cpiFileRef ); + LFA_Read ( cpiFileRef, &extensionDataHeader.mProMetaIDBlock.mProfessionalMetaID, 16); + + return true; +} + +// ================================================================================================= +// ReadAVCCAMProClipInfo-- read Panasonic's proprietary PRO_ClipInfo block +// ============ + +static bool ReadAVCCAMProClipInfo ( LFA_FileRef& cpiFileRef, XMP_Uns8 tagID, AVCHD_blkPanasonicPrivateData& extensionDataHeader ) +{ + extensionDataHeader.mPresent = 1; + extensionDataHeader.mProClipIDBlock.mPresent = 1; + extensionDataHeader.mProClipIDBlock.mTagID = tagID; + LFA_Read ( cpiFileRef, &extensionDataHeader.mProClipIDBlock.mTagVersion, 1); + extensionDataHeader.mProClipIDBlock.mTagLength = LFA_ReadUns16_BE ( cpiFileRef ); + LFA_Read ( cpiFileRef, &extensionDataHeader.mProClipIDBlock.mGlobalClipID, 32); + LFA_Read ( cpiFileRef, &extensionDataHeader.mProClipIDBlock.mStartTimecode, 4 ); + extensionDataHeader.mProClipIDBlock.mStartBinaryGroup = LFA_ReadUns32_BE ( cpiFileRef ); + + return true; +} + +// ================================================================================================= +// ReadAVCCAM_blkPRO_ShotMark -- read Panasonic's proprietary PRO_ShotMark block +// ============ + +static bool ReadAVCCAM_blkPRO_ShotMark ( LFA_FileRef& mplFileRef, AVCCAM_blkProPlayListMark& proMark ) +{ + proMark.mShotMark.mPresent = 1; + LFA_Read ( mplFileRef, &proMark.mShotMark.mShotMark, 1); + LFA_Read ( mplFileRef, &proMark.mShotMark.mFillItem, 3); + + return true; +} + +// ================================================================================================= +// ReadAVCCAM_blkPRO_Access -- read Panasonic's proprietary PRO_Access block +// ============ + +static bool ReadAVCCAM_blkPRO_Access ( LFA_FileRef& mplFileRef, AVCCAM_blkProPlayListMark& proMark ) +{ + proMark.mAccess.mPresent = 1; + LFA_Read ( mplFileRef, &proMark.mAccess.mCreatorCharacterSet, 1 ); + LFA_Read ( mplFileRef, &proMark.mAccess.mCreatorLength, 1 ); + LFA_Read ( mplFileRef, &proMark.mAccess.mCreator, 32 ); + LFA_Read ( mplFileRef, &proMark.mAccess.mLastUpdatePersonCharacterSet, 1 ); + LFA_Read ( mplFileRef, &proMark.mAccess.mLastUpdatePersonLength, 1 ); + LFA_Read ( mplFileRef, &proMark.mAccess.mLastUpdatePerson, 32 ); + + return true; +} + +// ================================================================================================= +// ReadAVCCAM_blkPRO_Device -- read Panasonic's proprietary PRO_Device block +// ============ + +static bool ReadAVCCAM_blkPRO_Device ( LFA_FileRef& mplFileRef, AVCCAM_blkProPlayListMark& proMark ) +{ + proMark.mDevice.mPresent = 1; + proMark.mDevice.mMakerID = LFA_ReadUns16_BE ( mplFileRef ); + proMark.mDevice.mMakerModelCode = LFA_ReadUns16_BE ( mplFileRef ); + LFA_Read ( mplFileRef, &proMark.mDevice.mSerialNoCharacterCode, 1 ); + LFA_Read ( mplFileRef, &proMark.mDevice.mSerialNoLength, 1 ); + LFA_Read ( mplFileRef, &proMark.mDevice.mSerialNo, 24 ); + LFA_Read ( mplFileRef, &proMark.mDevice.mFillItem, 2 ); + + return true; +} + +// ================================================================================================= +// ReadAVCCAM_blkPRO_Shoot -- read Panasonic's proprietary PRO_Shoot block +// ============ + +static bool ReadAVCCAM_blkPRO_Shoot ( LFA_FileRef& mplFileRef, AVCCAM_blkProPlayListMark& proMark ) +{ + proMark.mShoot.mPresent = 1; + LFA_Read ( mplFileRef, &proMark.mShoot.mShooterCharacterSet, 1 ); + LFA_Read ( mplFileRef, &proMark.mShoot.mShooterLength, 1 ); + LFA_Read ( mplFileRef, &proMark.mShoot.mShooter, 32 ); + LFA_Read ( mplFileRef, &proMark.mShoot.mStartDateTimeZone, 1 ); + LFA_Read ( mplFileRef, &proMark.mShoot.mStartDate, 7 ); + LFA_Read ( mplFileRef, &proMark.mShoot.mEndDateTimeZone, 1 ); + LFA_Read ( mplFileRef, &proMark.mShoot.mEndDate, 7 ); + LFA_Read ( mplFileRef, &proMark.mShoot.mFillItem, 2 ); + + return true; +} + +// ================================================================================================= +// ReadAVCCAM_blkPRO_Location -- read Panasonic's proprietary PRO_Location block +// ============ + +static bool ReadAVCCAM_blkPRO_Location ( LFA_FileRef& mplFileRef, AVCCAM_blkProPlayListMark& proMark ) +{ + proMark.mLocation.mPresent = 1; + LFA_Read ( mplFileRef, &proMark.mLocation.mSource, 1 ); + proMark.mLocation.mGPSLatitudeRef = LFA_ReadUns32_BE ( mplFileRef ); + proMark.mLocation.mGPSLatitude1 = LFA_ReadUns32_BE ( mplFileRef ); + proMark.mLocation.mGPSLatitude2 = LFA_ReadUns32_BE ( mplFileRef ); + proMark.mLocation.mGPSLatitude3 = LFA_ReadUns32_BE ( mplFileRef ); + proMark.mLocation.mGPSLongitudeRef = LFA_ReadUns32_BE ( mplFileRef ); + proMark.mLocation.mGPSLongitude1 = LFA_ReadUns32_BE ( mplFileRef ); + proMark.mLocation.mGPSLongitude2 = LFA_ReadUns32_BE ( mplFileRef ); + proMark.mLocation.mGPSLongitude3 = LFA_ReadUns32_BE ( mplFileRef ); + proMark.mLocation.mGPSAltitudeRef = LFA_ReadUns32_BE ( mplFileRef ); + proMark.mLocation.mGPSAltitude = LFA_ReadUns32_BE ( mplFileRef ); + LFA_Read ( mplFileRef, &proMark.mLocation.mPlaceNameCharacterSet, 1 ); + LFA_Read ( mplFileRef, &proMark.mLocation.mPlaceNameLength, 1 ); + LFA_Read ( mplFileRef, &proMark.mLocation.mPlaceName, 64 ); + LFA_Read ( mplFileRef, &proMark.mLocation.mFillItem, 1 ); + + return true; +} + +// ================================================================================================= +// ReadAVCCAMProPlaylistInfo -- read Panasonic's proprietary PRO_PlayListInfo block +// ============ + +static bool ReadAVCCAMProPlaylistInfo ( LFA_FileRef& mplFileRef, + XMP_Uns8 tagID, + XMP_Uns16 playlistMarkID, + AVCHD_blkPanasonicPrivateData& extensionDataHeader ) +{ + AVCCAM_Pro_PlayListInfo& playlistBlock = extensionDataHeader.mProPlaylistInfoBlock; + + playlistBlock.mTagID = tagID; + LFA_Read ( mplFileRef, &playlistBlock.mTagVersion, 1); + LFA_Read ( mplFileRef, &playlistBlock.mFillItem1, 2); + playlistBlock.mLength = LFA_ReadUns32_BE ( mplFileRef ); + playlistBlock.mNumberOfPlayListMarks = LFA_ReadUns16_BE ( mplFileRef ); + LFA_Read ( mplFileRef, &playlistBlock.mFillItem2, 2); + + if ( playlistBlock.mNumberOfPlayListMarks == 0 ) return true; + + extensionDataHeader.mPresent = 1; + + XMP_Uns64 blockStart = 0; + + for ( int i = 0; i < playlistBlock.mNumberOfPlayListMarks; ++i ) { + AVCCAM_blkProPlayListMark& currMark = playlistBlock.mPlayListMark; + + LFA_Read ( mplFileRef, &currMark.mProTagID, 1); + LFA_Read ( mplFileRef, &currMark.mFillItem1, 1); + currMark.mLength = LFA_ReadUns16_BE ( mplFileRef ); + blockStart = LFA_Tell ( mplFileRef ); + LFA_Read ( mplFileRef, &currMark.mMarkType, 1 ); + + if ( ( currMark.mProTagID == 0x40 ) && ( currMark.mMarkType == 0x01 ) ) { + LFA_Read ( mplFileRef, &currMark.mEntryMark.mGlobalClipID, 32); + + // skip marks for different clips + if ( i == playlistMarkID ) { + playlistBlock.mPresent = 1; + currMark.mPresent = 1; + LFA_Read ( mplFileRef, &currMark.mEntryMark.mStartTimeCode, 4); + LFA_Read ( mplFileRef, &currMark.mEntryMark.mStreamTimecodeInfo, 1); + LFA_Read ( mplFileRef, &currMark.mEntryMark.mStartBinaryGroup, 4); + LFA_Read ( mplFileRef, &currMark.mEntryMark.mLastUpdateTimeZone, 1); + LFA_Read ( mplFileRef, &currMark.mEntryMark.mLastUpdateDate, 7); + LFA_Read ( mplFileRef, &currMark.mEntryMark.mFillItem, 2); + + XMP_Uns64 currPos = LFA_Tell ( mplFileRef ); + XMP_Uns8 blockTag = 0; + XMP_Uns8 blockFill; + XMP_Uns16 blockLength = 0; + + while ( currPos < ( blockStart + currMark.mLength ) ) { + LFA_Read ( mplFileRef, &blockTag, 1); + LFA_Read ( mplFileRef, &blockFill, 1); + blockLength = LFA_ReadUns16_BE ( mplFileRef ); + currPos += 4; + + switch ( blockTag ) { + case 0x20: + if ( ! ReadAVCCAM_blkPRO_ShotMark ( mplFileRef, currMark ) ) return false; + break; + + + case 0x21: + if ( ! ReadAVCCAM_blkPRO_Access ( mplFileRef, currMark ) ) return false; + break; + + case 0x22: + if ( ! ReadAVCCAM_blkPRO_Device ( mplFileRef, currMark ) ) return false; + break; + + case 0x23: + if ( ! ReadAVCCAM_blkPRO_Shoot ( mplFileRef, currMark ) ) return false; + break; + + case 0x24: + if (! ReadAVCCAM_blkPRO_Location ( mplFileRef, currMark ) ) return false; + break; + + default : break; + } + + currPos += blockLength; + LFA_Seek ( mplFileRef, currPos, SEEK_SET ); + } + } + } + + LFA_Seek ( mplFileRef, blockStart + currMark.mLength, SEEK_SET ); + } + + return true; +} + +// ================================================================================================= +// ReadAVCCAMMakersPrivateData -- read Panasonic's implementation of an AVCCAM "Maker's Private Data" +// block. Panasonic calls their extensions "AVCCAM." +// ============ + +static bool ReadAVCCAMMakersPrivateData ( LFA_FileRef& fileRef, + XMP_Uns16 playlistMarkID, + AVCHD_blkPanasonicPrivateData& avccamPrivateData ) +{ + const XMP_Uns64 blockStart = LFA_Tell(fileRef); + + avccamPrivateData.mNumberOfData = LFA_ReadUns16_BE ( fileRef ); + LFA_Read ( fileRef, &avccamPrivateData.mReserved, 2 ); + + for (int i = 0; i < avccamPrivateData.mNumberOfData; ++i) { + const XMP_Uns8 tagID = LFA_ReadUns8 ( fileRef ); + + switch ( tagID ) { + case 0xe0: ReadAVCCAMProMetaID ( fileRef, tagID, avccamPrivateData ); + break; + case 0xe2: ReadAVCCAMProClipInfo( fileRef, tagID, avccamPrivateData ); + break; + case 0xf0: ReadAVCCAMProPlaylistInfo( fileRef, tagID, playlistMarkID, avccamPrivateData ); + break; + + default: + // Ignore any blocks we don't now or care about + break; + } + } + + return true; +} + +// ================================================================================================= +// ReadAVCHDMakersPrivateData (AVCHD Format. Book 2: Recording Extension Specifications, section 4.2.4.2) +// ============ + +static bool ReadAVCHDMakersPrivateData ( LFA_FileRef& mplFileRef, + XMP_Uns16 playlistMarkID, + AVCHD_blkMakersPrivateData& avchdLegacyData ) +{ + const XMP_Uns64 blockStart = LFA_Tell ( mplFileRef ); + + avchdLegacyData.mLength = LFA_ReadUns32_BE ( mplFileRef ); + + if ( avchdLegacyData.mLength == 0 ) return false; + + avchdLegacyData.mPresent = 1; + avchdLegacyData.mDataBlockStartAddress = LFA_ReadUns32_BE ( mplFileRef ); + LFA_Read ( mplFileRef, &avchdLegacyData.mReserved, 3 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mNumberOfMakerEntries, 1 ); + + if ( avchdLegacyData.mNumberOfMakerEntries == 0 ) return true; + + XMP_Uns16 makerID; + XMP_Uns16 makerModelCode; + XMP_Uns32 mpdStartAddress; + XMP_Uns32 mpdLength; + + for ( int i = 0; i < avchdLegacyData.mNumberOfMakerEntries; ++i ) { + makerID = LFA_ReadUns16_BE ( mplFileRef ); + makerModelCode = LFA_ReadUns16_BE ( mplFileRef ); + mpdStartAddress = LFA_ReadUns32_BE ( mplFileRef ); + mpdLength = LFA_ReadUns32_BE ( mplFileRef ); + + // We only have documentation for Panasonic's Maker's Private Data blocks, so we'll ignore everyone else's + if ( makerID == kMakerIDPanasonic ) { + avchdLegacyData.mMakerID = makerID; + avchdLegacyData.mMakerModelCode = makerModelCode; + LFA_Seek ( mplFileRef, blockStart + mpdStartAddress, SEEK_SET ); + + if (! ReadAVCCAMMakersPrivateData ( mplFileRef, playlistMarkID, avchdLegacyData.mPanasonicPrivateData ) ) return false; + } + } + + return true; +} + +// ================================================================================================= +// ReadAVCHDClipExtensionData +// ============ + +static bool ReadAVCHDClipExtensionData ( LFA_FileRef& cpiFileRef, AVCHD_blkClipExtensionData& avchdExtensionData ) +{ + const XMP_Int64 extensionBlockStart = LFA_Tell ( cpiFileRef ); + AVCHD_blkExtensionData extensionDataHeader; + + if ( ! ReadAVCHDExtensionData ( cpiFileRef, extensionDataHeader ) ) { + return false; + } + + if ( extensionDataHeader.mLength == 0 ) { + return true; + } + + const XMP_Int64 dataBlockStart = extensionBlockStart + extensionDataHeader.mDataBlockStartAddress; + + LFA_Seek ( cpiFileRef, dataBlockStart, SEEK_SET ); + LFA_Read ( cpiFileRef, avchdExtensionData.mTypeIndicator, 4 ); + + if ( strncmp ( reinterpret_cast<const char*>( avchdExtensionData.mTypeIndicator ), "CLEX", 4 ) != 0 ) return false; + + avchdExtensionData.mPresent = 1; + LFA_Read ( cpiFileRef, avchdExtensionData.mReserved1, 4 ); + avchdExtensionData.mProgramInfoExtStartAddress = LFA_ReadUns32_BE ( cpiFileRef ); + avchdExtensionData.mMakersPrivateDataStartAddress = LFA_ReadUns32_BE ( cpiFileRef ); + + // read Clip info extension + LFA_Seek ( cpiFileRef, dataBlockStart + 40, SEEK_SET ); + avchdExtensionData.mClipInfoExt.mLength = LFA_ReadUns32_BE ( cpiFileRef ); + avchdExtensionData.mClipInfoExt.mMakerID = LFA_ReadUns16_BE ( cpiFileRef ); + avchdExtensionData.mClipInfoExt.mMakerModelCode = LFA_ReadUns16_BE ( cpiFileRef ); + + if ( avchdExtensionData.mMakersPrivateDataStartAddress == 0 ) return true; + + if ( avchdExtensionData.mClipInfoExt.mMakerID == kMakerIDPanasonic ) { + // Read Maker's Private Data block -- we only have Panasonic's definition for their AVCCAM models + // at this point, so we'll ignore the block if its from a different manufacturer. + LFA_Seek ( cpiFileRef, dataBlockStart + avchdExtensionData.mMakersPrivateDataStartAddress, SEEK_SET ); + + if ( ! ReadAVCHDMakersPrivateData ( cpiFileRef, 0, avchdExtensionData.mMakersPrivateData ) ) { + return false; + } + } + + return true; +} + +// ================================================================================================= +// AVCHD_PlaylistContainsClip -- returns true of the specified AVCHD playlist block references the +// specified clip, or false if not. +// ==================== + +static bool AVCHD_PlaylistContainsClip ( LFA_FileRef& mplFileRef, XMP_Uns16& playItemID, const std::string& strClipName ) +{ + // Read clip header. ( AVCHD Format. Book1. v. 1.01. p 45 ) + struct AVCHD_blkPlayList + { + XMP_Uns32 mLength; + XMP_Uns16 mReserved; + XMP_Uns16 mNumberOfPlayItems; + XMP_Uns16 mNumberOfSubPaths; + }; + + AVCHD_blkPlayList blkPlayList; + blkPlayList.mLength = LFA_ReadUns32_BE ( mplFileRef ); + LFA_Read ( mplFileRef, &blkPlayList.mReserved, 2 ); + blkPlayList.mNumberOfPlayItems = LFA_ReadUns16_BE ( mplFileRef ); + blkPlayList.mNumberOfSubPaths = LFA_ReadUns16_BE ( mplFileRef ); + + // Search the play items. ( AVCHD Format. Book1. v. 1.01. p 47 ) + struct AVCHD_blkPlayItem + { + XMP_Uns16 mLength; + char mClipInformationFileName[5]; + // Note: remaining fields omitted because we don't care about them + }; + + AVCHD_blkPlayItem currPlayItem; + XMP_Uns64 blockStart = 0; + for ( playItemID = 0; playItemID < blkPlayList.mNumberOfPlayItems; ++playItemID ) { + currPlayItem.mLength = LFA_ReadUns16_BE ( mplFileRef ); + + // mLength is measured from the end of mLength, not the start of the block ( AVCHD Format. Book1. v. 1.01. p 47 ) + blockStart = LFA_Tell ( mplFileRef ); + LFA_Read ( mplFileRef, currPlayItem.mClipInformationFileName, 5 ); + + if ( strncmp ( strClipName.c_str(), currPlayItem.mClipInformationFileName, 5 ) == 0 ) return true; + + LFA_Seek ( mplFileRef, blockStart + currPlayItem.mLength, SEEK_SET ); + } + + return false; +} + +// ================================================================================================= +// ReadAVCHDPlaylistMetadataBlock +// ============ + +static bool ReadAVCHDPlaylistMetadataBlock ( LFA_FileRef& mplFileRef, + AVCHD_blkPlaylistMeta& avchdLegacyData ) +{ + avchdLegacyData.mLength = LFA_ReadUns32_BE ( mplFileRef ); + + if ( avchdLegacyData.mLength < sizeof ( AVCHD_blkPlaylistMeta ) ) return false; + + avchdLegacyData.mMakerID = LFA_ReadUns16_BE ( mplFileRef ); + avchdLegacyData.mMakerModelCode = LFA_ReadUns16_BE ( mplFileRef ); + LFA_Read ( mplFileRef, &avchdLegacyData.mReserved1, 4 ); + avchdLegacyData.mRefToMenuThumbnailIndex = LFA_ReadUns16_BE ( mplFileRef ); + LFA_Read ( mplFileRef, &avchdLegacyData.mBlkTimezone, 1 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mRecordDataAndTime, 7 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mReserved2, 1 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mPlaylistCharacterSet, 1 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mPlaylistNameLength, 1 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mPlaylistName, avchdLegacyData.mPlaylistNameLength ); + + return true; +} + +// ================================================================================================= +// ReadAVCHDPlaylistMarkExtension +// ============ + +static bool ReadAVCHDPlaylistMarkExtension ( LFA_FileRef& mplFileRef, + XMP_Uns16 playlistMarkID, + AVCHD_blkPlayListMarkExt& avchdLegacyData ) +{ + avchdLegacyData.mLength = LFA_ReadUns32_BE ( mplFileRef ); + + if ( avchdLegacyData.mLength == 0 ) return false; + + avchdLegacyData.mNumberOfPlaylistMarks = LFA_ReadUns16_BE ( mplFileRef ); + + if ( avchdLegacyData.mNumberOfPlaylistMarks <= playlistMarkID ) return true; + + // Number of bytes in blkMarkExtension, AVCHD Book 2, section 4.3.3.1 + const XMP_Uns64 markExtensionSize = 66; + + // Entries in the mark extension block correspond one-to-one with entries in + // blkPlaylistMark, so we'll only read the one that corresponds to the + // chosen clip. + const XMP_Uns64 markOffset = markExtensionSize * playlistMarkID; + + avchdLegacyData.mPresent = 1; + LFA_Seek ( mplFileRef, markOffset, SEEK_CUR ); + avchdLegacyData.mMakerID = LFA_ReadUns16_BE ( mplFileRef ); + avchdLegacyData.mMakerModelCode = LFA_ReadUns16_BE ( mplFileRef ); + LFA_Read ( mplFileRef, &avchdLegacyData.mReserved1, 3 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mFlags, 1 ); + avchdLegacyData.mRefToMarkThumbnailIndex = LFA_ReadUns16_BE ( mplFileRef ); + LFA_Read ( mplFileRef, &avchdLegacyData.mBlkTimezone, 1 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mRecordDataAndTime, 7 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mMarkCharacterSet, 1 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mMarkNameLength, 1 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mMarkName, 24 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mMakersInformation, 16 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mBlkTimecode, 4 ); + LFA_Read ( mplFileRef, &avchdLegacyData.mReserved2, 2 ); + + return true; +} + +// ================================================================================================= +// ReadAVCHDPlaylistMarkID -- read the playlist mark block to find the ID of the playlist mark that +// matches the specified playlist item. +// ============ + +static bool ReadAVCHDPlaylistMarkID ( LFA_FileRef& mplFileRef, + XMP_Uns16 playItemID, + XMP_Uns16& markID ) +{ + XMP_Uns32 length = LFA_ReadUns32_BE ( mplFileRef ); + XMP_Uns16 numberOfPlayListMarks = LFA_ReadUns16_BE ( mplFileRef ); + + if ( length == 0 ) return false; + + XMP_Uns8 reserved; + XMP_Uns8 markType; + XMP_Uns16 refToPlayItemID; + + for ( int i = 0; i < numberOfPlayListMarks; ++i ) { + LFA_Read ( mplFileRef, &reserved, 1 ); + LFA_Read ( mplFileRef, &markType, 1 ); + refToPlayItemID = LFA_ReadUns16_BE ( mplFileRef ); + + if ( ( markType == 0x01 ) && ( refToPlayItemID == playItemID ) ) { + markID = i; + return true; + } + + LFA_Seek ( mplFileRef, 10, SEEK_CUR ); + } + + return false; +} + +// ================================================================================================= +// ReadAVCHDPlaylistExtensionData +// ============ + +static bool ReadAVCHDPlaylistExtensionData ( LFA_FileRef& mplFileRef, + AVCHD_LegacyMetadata& avchdLegacyData, + XMP_Uns16 playlistMarkID ) +{ + const XMP_Int64 extensionBlockStart = LFA_Tell ( mplFileRef ); + AVCHD_blkExtensionData extensionDataHeader; + + if ( ! ReadAVCHDExtensionData ( mplFileRef, extensionDataHeader ) ) { + return false; + } + + if ( extensionDataHeader.mLength == 0 ) { + return true; + } + + const XMP_Int64 dataBlockStart = extensionBlockStart + extensionDataHeader.mDataBlockStartAddress; + AVCHD_blkPlayListExtensionData& extensionData = avchdLegacyData.mPlaylistExtensionData; + const int reserved2Len = 24; + + LFA_Seek ( mplFileRef, dataBlockStart, SEEK_SET ); + LFA_Read ( mplFileRef, extensionData.mTypeIndicator, 4 ); + + if ( strncmp ( extensionData.mTypeIndicator, "PLEX", 4 ) != 0 ) return false; + + extensionData.mPresent = true; + LFA_Read ( mplFileRef, extensionData.mReserved, 4 ); + extensionData.mPlayListMarkExtStartAddress = LFA_ReadUns32_BE ( mplFileRef ); + extensionData.mMakersPrivateDataStartAddress = LFA_ReadUns32_BE ( mplFileRef ); + LFA_Seek ( mplFileRef, reserved2Len, SEEK_CUR ); + + if ( ! ReadAVCHDPlaylistMetadataBlock ( mplFileRef, extensionData.mPlaylistMeta ) ) return false; + + LFA_Seek ( mplFileRef, dataBlockStart + extensionData.mPlayListMarkExtStartAddress, SEEK_SET ); + + if ( ! ReadAVCHDPlaylistMarkExtension ( mplFileRef, playlistMarkID, extensionData.mPlaylistMarkExt ) ) return false; + + if ( extensionData.mMakersPrivateDataStartAddress > 0 ) { + + if ( ! avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPanasonicPrivateData.mPresent ) return false; + + LFA_Seek ( mplFileRef, dataBlockStart + extensionData.mMakersPrivateDataStartAddress, SEEK_SET ); + + if ( ! ReadAVCHDMakersPrivateData ( mplFileRef, playlistMarkID, extensionData.mMakersPrivateData ) ) return false; + + } + + return true; +} + +// ================================================================================================= +// ReadAVCHDLegacyClipFile -- read the legacy metadata stored in an AVCHD .CPI file // ==================== -static bool ReadAVCHDProgramInfo ( const std::string& strPath, AVCHD_blkProgramInfo& avchdProgramInfo ) +static bool ReadAVCHDLegacyClipFile ( const std::string& strPath, AVCHD_LegacyMetadata& avchdLegacyData ) { + bool success = false; + try { - AutoFile idxFile; - idxFile.fileRef = LFA_Open ( strPath.c_str(), 'r' ); - if ( idxFile.fileRef == 0 ) return false; // The open failed. + AutoFile cpiFile; + cpiFile.fileRef = LFA_Open ( strPath.c_str(), 'r' ); + if ( cpiFile.fileRef == 0 ) return false; // The open failed. - memset ( &avchdProgramInfo, 0, sizeof(AVCHD_blkProgramInfo) ); + memset ( &avchdLegacyData, 0, sizeof(AVCHD_LegacyMetadata) ); - // Read clip header. (AVCHD Format. Book1. v. 1.01. p 64 ) + // Read clip header. ( AVCHD Format. Book1. v. 1.01. p 64 ) struct AVCHD_ClipInfoHeader { - XMP_Uns8 mTypeIndicator[4]; - XMP_Uns8 mTypeIndicator2[4]; + char mTypeIndicator[4]; + char mTypeIndicator2[4]; XMP_Uns32 mSequenceInfoStartAddress; XMP_Uns32 mProgramInfoStartAddress; - XMP_Uns32 mCPIStartAddress; + XMP_Uns32 mCPIStartAddress; + XMP_Uns32 mClipMarkStartAddress; XMP_Uns32 mExtensionDataStartAddress; XMP_Uns8 mReserved[12]; }; // Read the AVCHD header. AVCHD_ClipInfoHeader avchdHeader; - LFA_Read ( idxFile.fileRef, avchdHeader.mTypeIndicator, 4 ); - LFA_Read ( idxFile.fileRef, avchdHeader.mTypeIndicator2, 4 ); - avchdHeader.mSequenceInfoStartAddress = LFA_ReadUns32_BE ( idxFile.fileRef ); - avchdHeader.mProgramInfoStartAddress = LFA_ReadUns32_BE ( idxFile.fileRef ); - avchdHeader.mCPIStartAddress = LFA_ReadUns32_BE ( idxFile.fileRef ); - avchdHeader.mExtensionDataStartAddress = LFA_ReadUns32_BE ( idxFile.fileRef ); - LFA_Read ( idxFile.fileRef, avchdHeader.mReserved, 12 ); + LFA_Read ( cpiFile.fileRef, avchdHeader.mTypeIndicator, 4 ); + LFA_Read ( cpiFile.fileRef, avchdHeader.mTypeIndicator2, 4 ); + + if ( strncmp ( avchdHeader.mTypeIndicator, "HDMV", 4 ) != 0 ) return false; + if ( strncmp ( avchdHeader.mTypeIndicator2, "0100", 4 ) != 0 ) return false; + + avchdHeader.mSequenceInfoStartAddress = LFA_ReadUns32_BE ( cpiFile.fileRef ); + avchdHeader.mProgramInfoStartAddress = LFA_ReadUns32_BE ( cpiFile.fileRef ); + avchdHeader.mCPIStartAddress = LFA_ReadUns32_BE ( cpiFile.fileRef ); + avchdHeader.mClipMarkStartAddress = LFA_ReadUns32_BE ( cpiFile.fileRef ); + avchdHeader.mExtensionDataStartAddress = LFA_ReadUns32_BE ( cpiFile.fileRef ); + LFA_Read ( cpiFile.fileRef, avchdHeader.mReserved, 12 ); // Seek to the program header. (AVCHD Format. Book1. v. 1.01. p 77 ) - LFA_Seek ( idxFile.fileRef, avchdHeader.mProgramInfoStartAddress, SEEK_SET ); + LFA_Seek ( cpiFile.fileRef, avchdHeader.mProgramInfoStartAddress, SEEK_SET ); - avchdProgramInfo.mLength = LFA_ReadUns32_BE ( idxFile.fileRef ); - LFA_Read ( idxFile.fileRef, avchdProgramInfo.mReserved1, 2 ); - avchdProgramInfo.mSPNProgramSequenceStart = LFA_ReadUns32_BE ( idxFile.fileRef ); - avchdProgramInfo.mProgramMapPID = LFA_ReadUns16_BE ( idxFile.fileRef ); - LFA_Read ( idxFile.fileRef, &avchdProgramInfo.mNumberOfStreamsInPS, 1 ); - LFA_Read ( idxFile.fileRef, &avchdProgramInfo.mReserved2, 1 ); + // Read the program info block + success = ReadAVCHDProgramInfo ( cpiFile.fileRef, avchdLegacyData.mProgramInfo ); + + if ( success && ( avchdHeader.mExtensionDataStartAddress != 0 ) ) { + // Seek to the program header. (AVCHD Format. Book1. v. 1.01. p 77 ) + LFA_Seek ( cpiFile.fileRef, avchdHeader.mExtensionDataStartAddress, SEEK_SET ); + success = ReadAVCHDClipExtensionData ( cpiFile.fileRef, avchdLegacyData.mClipExtensionData ); + } + + } catch ( ... ) { - XMP_Uns16 streamPID = 0; - for ( int i=0; i<avchdProgramInfo.mNumberOfStreamsInPS; ++i ) { + return false; - XMP_Uns8 length = 0; - XMP_Uns8 streamCodingType = 0; + } - streamPID = LFA_ReadUns16_BE ( idxFile.fileRef ); - LFA_Read ( idxFile.fileRef, &length, 1 ); + return success; +} - XMP_Int64 pos = LFA_Tell ( idxFile.fileRef ); +// ================================================================================================= +// ReadAVCHDLegacyPlaylistFile -- read the legacy metadata stored in an AVCHD .MPL file +// ==================== - LFA_Read ( idxFile.fileRef, &streamCodingType, 1 ); +static bool ReadAVCHDLegacyPlaylistFile ( const std::string& strRootPath, + const std::string& strClipName, + AVCHD_LegacyMetadata& avchdLegacyData ) +{ + bool success = false; + std::string mplPath; + char playlistName [10]; + const int rootPlaylistNum = atoi(strClipName.c_str()); + + // Find the corresponding .MPL file -- because of clip spanning the .MPL name may not match the .CPI name for + // a given clip -- we need to open .MPL files and look for one that contains a reference to the clip name. To speed + // up the search we'll start with the playlist with the same number/name as the clip and search backwards. Assuming + // this directory was generated by a camera, the clip numbers will increase sequentially across the playlist files, + // though one playlist file may reference more than one clip. + for ( int i = rootPlaylistNum; i >= 0; --i ) { + + sprintf ( playlistName, "%05d", i ); + + if ( MakeLeafPath ( &mplPath, strRootPath.c_str(), "PLAYLIST", playlistName, ".mpl", true /* checkFile */ ) ) { + + try { + + AutoFile mplFile; + mplFile.fileRef = LFA_Open ( mplPath.c_str(), 'r' ); + if ( mplFile.fileRef == 0 ) return false; // The open failed. + + // Read playlist header. ( AVCHD Format. Book1. v. 1.01. p 43 ) + struct AVCHD_PlaylistFileHeader + { + char mTypeIndicator[4]; + char mTypeIndicator2[4]; + XMP_Uns32 mPlaylistStartAddress; + XMP_Uns32 mPlaylistMarkStartAddress; + XMP_Uns32 mExtensionDataStartAddress; + }; + + // Read the AVCHD playlist file header. + AVCHD_PlaylistFileHeader avchdHeader; + LFA_Read ( mplFile.fileRef, avchdHeader.mTypeIndicator, 4 ); + LFA_Read ( mplFile.fileRef, avchdHeader.mTypeIndicator2, 4 ); + + if ( strncmp ( avchdHeader.mTypeIndicator, "MPLS", 4 ) != 0 ) return false; + if ( strncmp ( avchdHeader.mTypeIndicator2, "0100", 4 ) != 0 ) return false; + + avchdHeader.mPlaylistStartAddress = LFA_ReadUns32_BE ( mplFile.fileRef ); + avchdHeader.mPlaylistMarkStartAddress = LFA_ReadUns32_BE ( mplFile.fileRef ); + avchdHeader.mExtensionDataStartAddress = LFA_ReadUns32_BE ( mplFile.fileRef ); + + if ( avchdHeader.mExtensionDataStartAddress == 0 ) return false; + + // Seek to the start of the Playlist block. (AVCHD Format. Book1. v. 1.01. p 45 ) + LFA_Seek ( mplFile.fileRef, avchdHeader.mPlaylistStartAddress, SEEK_SET ); + + XMP_Uns16 playItemID = 0xFFFF; + XMP_Uns16 playlistMarkID = 0xFFFF; + + if ( AVCHD_PlaylistContainsClip ( mplFile.fileRef, playItemID, strClipName ) ) { + LFA_Seek ( mplFile.fileRef, avchdHeader.mPlaylistMarkStartAddress, SEEK_SET ); + + if ( ! ReadAVCHDPlaylistMarkID ( mplFile.fileRef, playItemID, playlistMarkID ) ) return false; + + LFA_Seek ( mplFile.fileRef, avchdHeader.mExtensionDataStartAddress, SEEK_SET ); + success = ReadAVCHDPlaylistExtensionData ( mplFile.fileRef, avchdLegacyData, playlistMarkID ); + } + } catch ( ... ) { + + return false; - switch ( streamCodingType ) { + } + } + + } - case 0x1B : // Video stream case. - { - XMP_Uns8 videoFormatAndFrameRate; - LFA_Read ( idxFile.fileRef, &videoFormatAndFrameRate, 1 ); - avchdProgramInfo.mVideoStream.mVideoFormat = videoFormatAndFrameRate >> 4; // hi 4 bits - avchdProgramInfo.mVideoStream.mFrameRate = videoFormatAndFrameRate & 0x0f; // lo 4 bits - - XMP_Uns8 aspectRatioAndReserved = 0; - LFA_Read ( idxFile.fileRef, &aspectRatioAndReserved, 1 ); - avchdProgramInfo.mVideoStream.mAspectRatio = aspectRatioAndReserved >> 4; // hi 4 bits - - XMP_Uns8 ccFlag = 0; - LFA_Read ( idxFile.fileRef, &ccFlag, 1 ); - avchdProgramInfo.mVideoStream.mCCFlag = ccFlag; - - avchdProgramInfo.mVideoStream.mPresent = 1; - } - break; - - case 0x80 : // Fall through. - case 0x81 : // Audio stream case. - { - XMP_Uns8 audioPresentationTypeAndFrequency = 0; - LFA_Read ( idxFile.fileRef, &audioPresentationTypeAndFrequency, 1 ); - - avchdProgramInfo.mAudioStream.mAudioPresentationType = audioPresentationTypeAndFrequency >> 4; // hi 4 bits - avchdProgramInfo.mAudioStream.mSamplingFrequency = audioPresentationTypeAndFrequency & 0x0f; // lo 4 bits + return success; +} + +// ================================================================================================= +// ReadAVCHDLegacyMetadata -- read the legacy metadata stored in an AVCHD .CPI file +// ==================== + +static bool ReadAVCHDLegacyMetadata ( const std::string& strPath, + const std::string& strRootPath, + const std::string& strClipName, + AVCHD_LegacyMetadata& avchdLegacyData ) +{ + bool success = ReadAVCHDLegacyClipFile ( strPath, avchdLegacyData ); - LFA_Read ( idxFile.fileRef, avchdProgramInfo.mAudioStream.mAudioLanguageCode, 3 ); - avchdProgramInfo.mAudioStream.mAudioLanguageCode[3] = 0; + if ( success && avchdLegacyData.mClipExtensionData.mPresent ) { + success = ReadAVCHDLegacyPlaylistFile ( strRootPath, strClipName, avchdLegacyData ); + } + + return success; + +} // ReadAVCHDLegacyMetadata + +// ================================================================================================= +// AVCCAM_SetXMPStartTimecode +// ============================= + +static void AVCCAM_SetXMPStartTimecode ( SXMPMeta& xmpObj, const XMP_Uns8* avccamTimecode, XMP_Uns8 avchdFrameRate ) +{ + // Timecode in SMPTE 12M format, according to Panasonic's documentation + if ( *reinterpret_cast<const XMP_Uns32*>( avccamTimecode ) == 0xFFFFFFFF ) { + // 0xFFFFFFFF means timecode not specified + return; + } - avchdProgramInfo.mAudioStream.mPresent = 1; - } - break; + const XMP_Uns8 isColor = ( avccamTimecode[0] >> 7 ) & 0x01; + const XMP_Uns8 isDropFrame = ( avccamTimecode[0] >> 6 ) & 0x01; + const XMP_Uns8 frameTens = ( avccamTimecode[0] >> 4 ) & 0x03; + const XMP_Uns8 frameUnits = avccamTimecode[0] & 0x0f; + const XMP_Uns8 secondTens = ( avccamTimecode[1] >> 4 ) & 0x07; + const XMP_Uns8 secondUnits = avccamTimecode[1] & 0x0f; + const XMP_Uns8 minuteTens = ( avccamTimecode[2] >> 4 ) & 0x07; + const XMP_Uns8 minuteUnits = avccamTimecode[2] & 0x0f; + const XMP_Uns8 hourTens = ( avccamTimecode[3] >> 4 ) & 0x03; + const XMP_Uns8 hourUnits = avccamTimecode[3] & 0x0f; + char tcSeparator = ':'; + const char* dmTimeFormat = NULL; + const char* dmTimeScale = NULL; + const char* dmTimeSampleSize = NULL; + + switch ( avchdFrameRate ) { + case 1 : + // 23.976i + dmTimeFormat = "23976Timecode"; + dmTimeScale = "24000"; + dmTimeSampleSize = "1001"; + break; + + case 2 : + // 24p + dmTimeFormat = "24Timecode"; + dmTimeScale = "24"; + dmTimeSampleSize = "1"; + break; + + case 3 : + case 6 : + // 50i or 25p + dmTimeFormat = "25Timecode"; + dmTimeScale = "25"; + dmTimeSampleSize = "1"; + break; + + case 4 : + case 7 : + // 29.97p or 59.94i + if ( isDropFrame ) { + dmTimeFormat = "2997DropTimecode"; + tcSeparator = ';'; + } else { + dmTimeFormat = "2997NonDropTimecode"; + } + + dmTimeScale = "30000"; + dmTimeSampleSize = "1001"; + + break; + } - case 0x90 : // Overlay bitmap stream case. - LFA_Read ( idxFile.fileRef, &avchdProgramInfo.mOverlayBitmapStream.mOBLanguageCode, 3 ); - avchdProgramInfo.mOverlayBitmapStream.mOBLanguageCode[3] = 0; - avchdProgramInfo.mOverlayBitmapStream.mPresent = 1; - break; + if ( dmTimeFormat != NULL ) { + char timecodeBuff [12]; + + sprintf ( timecodeBuff, "%d%d%c%d%d%c%d%d%c%d%d", hourTens, hourUnits, tcSeparator, + minuteTens, minuteUnits, tcSeparator, secondTens, secondUnits, tcSeparator, frameTens, frameUnits); + + xmpObj.SetProperty( kXMP_NS_DM, "startTimeScale", dmTimeScale, kXMP_DeleteExisting ); + xmpObj.SetProperty( kXMP_NS_DM, "startTimeSampleSize", dmTimeSampleSize, kXMP_DeleteExisting ); + xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", timecodeBuff, 0 ); + xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", dmTimeFormat, 0 ); + } +} + +// ================================================================================================= +// AVCHD_SetXMPMakeAndModel +// ============================= + +static bool AVCHD_SetXMPMakeAndModel ( SXMPMeta& xmpObj, const AVCHD_blkClipExtensionData& clipExtData ) +{ + if ( ! clipExtData.mPresent ) return false; + + XMP_StringPtr xmpValue = 0; + + // Set the Make. Use a hex string for unknown makes. + { + char hexMakeNumber [7]; + + switch ( clipExtData.mClipInfoExt.mMakerID ) { + case kMakerIDCanon : xmpValue = "Canon"; break; + case kMakerIDPanasonic : xmpValue = "Panasonic"; break; + case kMakerIDSony : xmpValue = "Sony"; break; + default : + std::sprintf ( hexMakeNumber, "0x%04x", clipExtData.mClipInfoExt.mMakerID ); + xmpValue = hexMakeNumber; + + break; + } + + xmpObj.SetProperty ( kXMP_NS_TIFF, "Make", xmpValue, kXMP_DeleteExisting ); + } - case 0x91 : // Menu bitmap stream. - LFA_Read ( idxFile.fileRef, &avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode, 3 ); - avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode[3] = 0; - avchdProgramInfo.mMenuBitmapStream.mPresent = 1; - break; + // Set the Model number. Use a hex string for unknown model numbers so they can still be distinguished. + { + char hexModelNumber [7]; + + xmpValue = 0; + + switch ( clipExtData.mClipInfoExt.mMakerID ) { + case kMakerIDCanon : + switch ( clipExtData.mClipInfoExt.mMakerModelCode ) { + case 0x1000 : xmpValue = "HR10"; break; + case 0x2000 : xmpValue = "HG10"; break; + case 0x2001 : xmpValue = "HG21"; break; + case 0x3000 : xmpValue = "HF100"; break; + case 0x3003 : xmpValue = "HF S10"; break; + default : break; + } + break; + + case kMakerIDPanasonic : + switch ( clipExtData.mClipInfoExt.mMakerModelCode ) { + case 0x0202 : xmpValue = "HD-writer"; break; + case 0x0400 : xmpValue = "AG-HSC1U"; break; + case 0x0401 : xmpValue = "AG-HMC70"; break; + case 0x0410 : xmpValue = "AG-HMC150"; break; + case 0x0411 : xmpValue = "AG-HMC40"; break; + case 0x0412 : xmpValue = "AG-HMC80"; break; + case 0x0413 : xmpValue = "AG-3DA1"; break; + case 0x0414 : xmpValue = "AG-AF100"; break; + case 0x0450 : xmpValue = "AG-HMR10"; break; + case 0x0451 : xmpValue = "AJ-YCX250"; break; + case 0x0452 : xmpValue = "AG-MDR15"; break; + case 0x0490 : xmpValue = "AVCCAM Restorer"; break; + case 0x0491 : xmpValue = "AVCCAM Viewer"; break; + case 0x0492 : xmpValue = "AVCCAM Viewer for Mac"; break; + default : break; + } + + break; + + default : break; + } + + if ( ( xmpValue == 0 ) && ( clipExtData.mClipInfoExt.mMakerID != kMakerIDSony ) ) { + // Panasonic has said that if we don't have a string for the model number, they'd like to see the code + // anyway. We'll do the same for every manufacturer except Sony, who have said that they use + // the same model number for multiple cameras. + std::sprintf ( hexModelNumber, "0x%04x", clipExtData.mClipInfoExt.mMakerModelCode ); + xmpValue = hexModelNumber; + } + + if ( xmpValue != 0 ) xmpObj.SetProperty ( kXMP_NS_TIFF, "Model", xmpValue, kXMP_DeleteExisting ); + } - default : - break; + return true; +} - } +// ================================================================================================= +// AVCHD_StringFieldToXMP +// ============================= - LFA_Seek ( idxFile.fileRef, pos + length, SEEK_SET ); +static std::string AVCHD_StringFieldToXMP ( XMP_Uns8 avchdLength, + XMP_Uns8 avchdCharacterSet, + const XMP_Uns8* avchdField, + XMP_Uns8 avchdFieldSize ) +{ + std::string xmpString; + + if ( avchdCharacterSet == 0x02 ) { + // UTF-16, Big Endian + UTF8Unit utf8Name [512]; + const XMP_Uns8 avchdMaxChars = ( avchdFieldSize / 2); + size_t utf16Read; + size_t utf8Written; + + // The spec doesn't say whether AVCHD length fields count bytes or characters, so we'll + // clamp to the max number of UTF-16 characters just in case. + const int stringLength = ( avchdLength > avchdMaxChars ) ? avchdMaxChars : avchdLength; + + UTF16BE_to_UTF8 ( reinterpret_cast<const UTF16Unit*> ( avchdField ), stringLength, + utf8Name, 512, &utf16Read, &utf8Written ); + xmpString.assign ( reinterpret_cast<const char*> ( utf8Name ), utf8Written ); + } else { + // AVCHD supports many character encodings, but UTF-8 (0x01) and ASCII (0x90) are the only ones I've + // seen in the wild at this point. We'll treat the other character sets as UTF-8 on the assumption that + // at least a few characters will come across, and something is better than nothing. + const int stringLength = ( avchdLength > avchdFieldSize ) ? avchdFieldSize : avchdLength; - } + xmpString.assign ( reinterpret_cast<const char*> ( avchdField ), stringLength ); + } + + return xmpString; +} - } catch ( ... ) { +// ================================================================================================= +// AVCHD_SetXMPShotName +// ============================= - return false; +static void AVCHD_SetXMPShotName ( SXMPMeta& xmpObj, const AVCHD_blkPlayListMarkExt& markExt, const std::string& strClipName ) +{ + if ( markExt.mPresent ) { + const std::string shotName = AVCHD_StringFieldToXMP ( markExt.mMarkNameLength, markExt.mMarkCharacterSet, markExt.mMarkName, 24 ); + + if ( ! shotName.empty() ) xmpObj.SetProperty ( kXMP_NS_DC, "shotName", shotName.c_str(), kXMP_DeleteExisting ); + } +} + +// ================================================================================================= +// BytesToHex +// ============================= + +#define kHexDigits "0123456789ABCDEF" +static std::string BytesToHex ( const XMP_Uns8* inClipIDBytes, int inNumBytes ) +{ + const int numChars = ( inNumBytes * 2 ); + std::string hexStr; + + hexStr.reserve(numChars); + + for ( int i = 0; i < inNumBytes; ++i ) { + const XMP_Uns8 byte = inClipIDBytes[i]; + hexStr.push_back ( kHexDigits [byte >> 4] ); + hexStr.push_back ( kHexDigits [byte & 0xF] ); } + + return hexStr; +} - return true; +// ================================================================================================= +// AVCHD_DateFieldToXMP (AVCHD Format Book 2, section 4.2.2.2) +// ============================= -} // ReadAVCHDProgramInfo +static std::string AVCHD_DateFieldToXMP ( XMP_Uns8 avchdTimeZone, const XMP_Uns8* avchdDateTime ) +{ + const XMP_Uns8 daylightSavingsTime = ( avchdTimeZone >> 6 ) & 0x01; + const XMP_Uns8 timezoneSign = ( avchdTimeZone >> 5 ) & 0x01; + const XMP_Uns8 timezoneValue = ( avchdTimeZone >> 1 ) & 0x0F; + const XMP_Uns8 halfHourFlag = avchdTimeZone & 0x01; + int utcOffsetHours = 0; + unsigned int utcOffsetMinutes = 0; + + // It's not entirely clear how to interpret the daylightSavingsTime flag from the documentation -- my best + // guess is that it should only be used if trying to display local time, not the UTC-relative time that + // XMP specifies. + if ( timezoneValue != 0xF ) { + utcOffsetHours = timezoneSign ? -timezoneValue : timezoneValue; + utcOffsetMinutes = 30 * halfHourFlag; + } + + char dateBuff [26]; + + sprintf ( dateBuff, + "%01d%01d%01d%01d-%01d%01d-%01d%01dT%01d%01d:%01d%01d:%01d%01d%+02d:%02d", + (avchdDateTime[0] >> 4), (avchdDateTime[0] & 0x0F), + (avchdDateTime[1] >> 4), (avchdDateTime[1] & 0x0F), + (avchdDateTime[2] >> 4), (avchdDateTime[2] & 0x0F), + (avchdDateTime[3] >> 4), (avchdDateTime[3] & 0x0F), + (avchdDateTime[4] >> 4), (avchdDateTime[4] & 0x0F), + (avchdDateTime[5] >> 4), (avchdDateTime[5] & 0x0F), + (avchdDateTime[6] >> 4), (avchdDateTime[6] & 0x0F), + utcOffsetHours, utcOffsetMinutes ); + + return std::string(dateBuff); +} // ================================================================================================= // AVCHD_MetaHandlerCTor @@ -322,11 +1619,11 @@ AVCHD_MetaHandler::AVCHD_MetaHandler ( XMPFiles * _parent ) // Extract the root path and clip name. - XMP_Assert ( this->parent->handlerTemp != 0 ); + XMP_Assert ( this->parent->tempPtr != 0 ); - this->rootPath.assign ( (char*) this->parent->handlerTemp ); - free ( this->parent->handlerTemp ); - this->parent->handlerTemp = 0; + this->rootPath.assign ( (char*) this->parent->tempPtr ); + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; SplitLeafName ( &this->rootPath, &this->clipName ); @@ -339,9 +1636,9 @@ AVCHD_MetaHandler::AVCHD_MetaHandler ( XMPFiles * _parent ) AVCHD_MetaHandler::~AVCHD_MetaHandler() { - if ( this->parent->handlerTemp != 0 ) { - free ( this->parent->handlerTemp ); - this->parent->handlerTemp = 0; + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; } } // AVCHD_MetaHandler::~AVCHD_MetaHandler @@ -350,72 +1647,86 @@ AVCHD_MetaHandler::~AVCHD_MetaHandler() // AVCHD_MetaHandler::MakeClipInfoPath // =================================== -void AVCHD_MetaHandler::MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix ) +bool AVCHD_MetaHandler::MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) const { - - *path = this->rootPath; - *path += kDirChar; - *path += "BDMV"; - *path += kDirChar; - *path += "CLIPINF"; - *path += kDirChar; - *path += this->clipName; - *path += suffix; - + return MakeLeafPath ( path, this->rootPath.c_str(), "CLIPINF", this->clipName.c_str(), suffix, checkFile ); } // AVCHD_MetaHandler::MakeClipInfoPath // ================================================================================================= // AVCHD_MetaHandler::MakeClipStreamPath // ===================================== -void AVCHD_MetaHandler::MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix ) +bool AVCHD_MetaHandler::MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) const { + return MakeLeafPath ( path, this->rootPath.c_str(), "STREAM", this->clipName.c_str(), suffix, checkFile ); +} // AVCHD_MetaHandler::MakeClipStreamPath - *path = this->rootPath; - *path += kDirChar; - *path += "BDMV"; - *path += kDirChar; - *path += "STREAM"; - *path += kDirChar; - *path += this->clipName; - *path += suffix; +// ================================================================================================= +// AVCHD_MetaHandler::MakePlaylistPath +// ===================================== -} // AVCHD_MetaHandler::MakeClipStreamPath +bool AVCHD_MetaHandler::MakePlaylistPath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) const +{ + return MakeLeafPath ( path, this->rootPath.c_str(), "PLAYLIST", this->clipName.c_str(), suffix, checkFile ); +} // AVCHD_MetaHandler::MakePlaylistPath // ================================================================================================= // AVCHD_MetaHandler::MakeLegacyDigest // =================================== -#define kHexDigits "0123456789ABCDEF" - void AVCHD_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) { - AVCHD_blkProgramInfo avchdProgramInfo; - std::string strPath; - this->MakeClipInfoPath ( &strPath, ".clpi" ); + std::string strClipPath; + std::string strPlaylistPath; + std::vector<XMP_Uns8> legacyBuff; - if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) { - this->MakeClipInfoPath ( &strPath, ".cpi" ); - if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) return; - } + bool ok = this->MakeClipInfoPath ( &strClipPath, ".clpi", true /* checkFile */ ); + if ( ! ok ) return; + ok = this->MakePlaylistPath ( &strPlaylistPath, ".mpls", true /* checkFile */ ); + if ( ! ok ) return; + + try { + { + AutoFile cpiFile; + cpiFile.fileRef = LFA_Open ( strClipPath.c_str(), 'r' ); + if ( cpiFile.fileRef == 0 ) return; // The open failed. + + // Read at most the first 2k of data from the cpi file to use in the digest + // (every CPI file I've seen is less than 1k). + const XMP_Int64 cpiLen = LFA_Measure ( cpiFile.fileRef ); + const XMP_Int64 buffLen = (cpiLen <= 2048) ? cpiLen : 2048; + + legacyBuff.resize ( (unsigned int) buffLen ); + LFA_Read ( cpiFile.fileRef, &(legacyBuff[0]), static_cast<XMP_Int32> ( buffLen ) ); + } + + { + AutoFile mplFile; + mplFile.fileRef = LFA_Open ( strPlaylistPath.c_str(), 'r' ); + if ( mplFile.fileRef == 0 ) return; // The open failed. + + // Read at most the first 2k of data from the cpi file to use in the digest + // (every playlist file I've seen is less than 1k). + const XMP_Int64 mplLen = LFA_Measure ( mplFile.fileRef ); + const XMP_Int64 buffLen = (mplLen <= 2048) ? mplLen : 2048; + const XMP_Int64 clipBuffLen = legacyBuff.size(); + + legacyBuff.resize ( (unsigned int) (clipBuffLen + buffLen) ); + LFA_Read ( mplFile.fileRef, &( legacyBuff [(unsigned int)clipBuffLen] ), (XMP_Int32)buffLen ); + } + } catch (...) { + return; + } + MD5_CTX context; unsigned char digestBin [16]; MD5Init ( &context ); - MD5Update ( &context, (XMP_Uns8*)&avchdProgramInfo, (unsigned int) sizeof(avchdProgramInfo) ); + MD5Update ( &context, (XMP_Uns8*)&(legacyBuff[0]), (unsigned int) legacyBuff.size() ); MD5Final ( digestBin, &context ); - char buffer [40]; - for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { - XMP_Uns8 byte = digestBin[in]; - buffer[out] = kHexDigits [ byte >> 4 ]; - buffer[out+1] = kHexDigits [ byte & 0xF ]; - } - buffer[32] = 0; - digestStr->erase(); - digestStr->append ( buffer, 32 ); - + *digestStr = BytesToHex ( digestBin, 16 ); } // AVCHD_MetaHandler::MakeLegacyDigest // ================================================================================================= @@ -424,13 +1735,13 @@ void AVCHD_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) void AVCHD_MetaHandler::CacheFileData() { - XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + XMP_Assert ( ! this->containsXMP ); // See if the clip's .XMP file exists. std::string xmpPath; - this->MakeClipStreamPath ( &xmpPath, ".xmp" ); - if ( GetFileMode ( xmpPath.c_str() ) != kFMode_IsFile ) return; // No XMP. + bool found = this->MakeClipStreamPath ( &xmpPath, ".xmp", true /* checkFile */ ); + if ( ! found ) return; // Read the entire .XMP file. @@ -481,50 +1792,50 @@ void AVCHD_MetaHandler::ProcessXMP() } // read clip info - AVCHD_blkProgramInfo avchdProgramInfo; + AVCHD_LegacyMetadata avchdLegacyData; std::string strPath; - this->MakeClipInfoPath ( &strPath, ".clpi" ); - if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) { - this->MakeClipInfoPath ( &strPath, ".cpi" ); - if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) return; + bool ok = this->MakeClipInfoPath ( &strPath, ".clpi", true /* checkFile */ ); + if ( ok ) ReadAVCHDLegacyMetadata ( strPath, this->rootPath, this->clipName, avchdLegacyData ); + if ( ! ok ) return; + + const AVCHD_blkPlayListMarkExt& markExt = avchdLegacyData.mPlaylistExtensionData.mPlaylistMarkExt; + XMP_Uns8 pulldownFlag = 0; + + if ( markExt.mPresent ) { + const std::string dateString = AVCHD_DateFieldToXMP ( markExt.mBlkTimezone, markExt.mRecordDataAndTime ); + + if ( ! dateString.empty() ) this->xmpObj.SetProperty ( kXMP_NS_DM, "shotDate", dateString.c_str(), kXMP_DeleteExisting ); + AVCHD_SetXMPShotName ( this->xmpObj, markExt, this->clipName ); + AVCCAM_SetXMPStartTimecode ( this->xmpObj, markExt.mBlkTimecode, avchdLegacyData.mProgramInfo.mVideoStream.mFrameRate ); + pulldownFlag = (markExt.mFlags >> 1) & 0x03; // bits 1 and 2 } // Video Stream. AVCHD Format v. 1.01 p. 78 + const bool has2_2pulldown = (pulldownFlag == 0x01); + const bool has3_2pulldown = (pulldownFlag == 0x10); XMP_StringPtr xmpValue = 0; - if ( avchdProgramInfo.mVideoStream.mPresent ) { - - // XMP videoFrameRate. - xmpValue = 0; - switch ( avchdProgramInfo.mVideoStream.mFrameRate ) { - case 1 : xmpValue = "23.98p"; break; // "23.976" - case 2 : xmpValue = "24p"; break; // "24" - case 3 : xmpValue = "25p"; break; // "25" - case 4 : xmpValue = "29.97p"; break; // "29.97" - case 6 : xmpValue = "50i"; break; // "50" - case 7 : xmpValue = "59.94i"; break; // "59.94" - default: break; - } - if ( xmpValue != 0 ) { - this->xmpObj.SetProperty ( kXMP_NS_DM, "videoFrameRate", xmpValue, kXMP_DeleteExisting ); - } - + if ( avchdLegacyData.mProgramInfo.mVideoStream.mPresent ) { + // XMP videoFrameSize. xmpValue = 0; int frameIndex = -1; + bool isProgressiveHD = false; const char* frameWidth[4] = { "720", "720", "1280", "1920" }; const char* frameHeight[4] = { "480", "576", "720", "1080" }; - switch ( avchdProgramInfo.mVideoStream.mVideoFormat ) { - case 1 : frameIndex = 0; break; // 480i - case 2 : frameIndex = 1; break; // 576i - case 3 : frameIndex = 0; break; // 480i - case 4 : frameIndex = 3; break; // 1080i - case 5 : frameIndex = 2; break; // 720p - case 6 : frameIndex = 3; break; // 1080p + + switch ( avchdLegacyData.mProgramInfo.mVideoStream.mVideoFormat ) { + case 1 : frameIndex = 0; break; // 480i + case 2 : frameIndex = 1; break; // 576i + case 3 : frameIndex = 0; break; // 480p + case 4 : frameIndex = 3; break; // 1080i + case 5 : frameIndex = 2; isProgressiveHD = true; break; // 720p + case 6 : frameIndex = 3; isProgressiveHD = true; break; // 1080p default: break; } + if ( frameIndex != -1 ) { xmpValue = frameWidth[frameIndex]; this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", xmpValue, 0 ); @@ -533,16 +1844,59 @@ void AVCHD_MetaHandler::ProcessXMP() xmpValue = "pixels"; this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", xmpValue, 0 ); } + + // XMP videoFrameRate. The logic below seems pretty tortured, but matches "Table 4-4 pulldown" on page 31 of Book 2 of the AVCHD + // spec, if you interepret "frame_mbs_only_flag" as "isProgressiveHD", "frame-rate [Hz]" as the frame rate encoded in + // mVideoStream.mFrameRate, and "Video Scan Type" as the desired xmp output value. The algorithm produces correct results for + // all the AVCHD media I've tested. + xmpValue = 0; + if ( isProgressiveHD ) { + + switch ( avchdLegacyData.mProgramInfo.mVideoStream.mFrameRate ) { + case 1 : xmpValue = "23.98p"; break; // "23.976" + case 2 : xmpValue = "24p"; break; // "24" + case 3 : xmpValue = "25p"; break; // "25" + case 4 : xmpValue = has2_2pulldown ? "29.97p" : "59.94p"; break; // "29.97" + case 6 : xmpValue = has2_2pulldown ? "25p" : "50p"; break; // "50" + case 7 : // "59.94" + if ( has2_2pulldown ) + xmpValue = "29.97p"; + else + xmpValue = has3_2pulldown ? "23.98p" : "59.94p"; + + break; + default: break; + } + + } else { + + switch ( avchdLegacyData.mProgramInfo.mVideoStream.mFrameRate ) { + case 3 : xmpValue = has2_2pulldown ? "25p" : "50i"; break; // "25" (but 1080p25 is reported as 1080i25 with 2:2 pulldown...) + case 4 : // "29.97" + if ( has2_2pulldown ) + xmpValue = "29.97p"; + else + xmpValue = has3_2pulldown ? "23.98p" : "59.94i"; + + break; + default: break; + } + + } + + if ( xmpValue != 0 ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "videoFrameRate", xmpValue, kXMP_DeleteExisting ); + } this->containsXMP = true; } // Audio Stream. - if ( avchdProgramInfo.mAudioStream.mPresent ) { + if ( avchdLegacyData.mProgramInfo.mAudioStream.mPresent ) { xmpValue = 0; - switch ( avchdProgramInfo.mAudioStream.mAudioPresentationType ) { + switch ( avchdLegacyData.mProgramInfo.mAudioStream.mAudioPresentationType ) { case 1 : xmpValue = "Mono"; break; case 3 : xmpValue = "Stereo"; break; default : break; @@ -552,7 +1906,7 @@ void AVCHD_MetaHandler::ProcessXMP() } xmpValue = 0; - switch ( avchdProgramInfo.mAudioStream.mSamplingFrequency ) { + switch ( avchdLegacyData.mProgramInfo.mAudioStream.mSamplingFrequency ) { case 1 : xmpValue = "48000"; break; case 4 : xmpValue = "96000"; break; case 5 : xmpValue = "192000"; break; @@ -563,7 +1917,64 @@ void AVCHD_MetaHandler::ProcessXMP() } this->containsXMP = true; + } + + // Proprietary vendor extensions + if ( AVCHD_SetXMPMakeAndModel ( this->xmpObj, avchdLegacyData.mClipExtensionData ) ) this->containsXMP = true; + + this->xmpObj.SetProperty ( kXMP_NS_DM, "title", this->clipName.c_str(), kXMP_DeleteExisting ); + this->containsXMP = true; + if ( avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPresent && + ( avchdLegacyData.mClipExtensionData.mClipInfoExt.mMakerID == kMakerIDPanasonic ) ) { + + const AVCHD_blkPanasonicPrivateData& panasonicClipData = avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPanasonicPrivateData; + + if ( panasonicClipData.mProClipIDBlock.mPresent ) { + const std::string globalClipIDString = BytesToHex ( panasonicClipData.mProClipIDBlock.mGlobalClipID, 32 ); + + this->xmpObj.SetProperty ( kXMP_NS_DC, "identifier", globalClipIDString.c_str(), kXMP_DeleteExisting ); + } + + const AVCHD_blkPanasonicPrivateData& panasonicPlaylistData = + avchdLegacyData.mPlaylistExtensionData.mMakersPrivateData.mPanasonicPrivateData; + + if ( panasonicPlaylistData.mProPlaylistInfoBlock.mPlayListMark.mPresent ) { + const AVCCAM_blkProPlayListMark& playlistMark = panasonicPlaylistData.mProPlaylistInfoBlock.mPlayListMark; + + if ( playlistMark.mShotMark.mPresent ) { + // Unlike P2, where "shotmark" is a boolean, Panasonic treats their AVCCAM shotmark as a bit field with + // 8 user-definable bits. For now we're going to treat any bit being set as xmpDM::good == true, and all + // bits being clear as xmpDM::good == false. + const bool isGood = ( playlistMark.mShotMark.mShotMark != 0 ); + + xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", isGood, kXMP_DeleteExisting ); + } + + if ( playlistMark.mAccess.mPresent && ( playlistMark.mAccess.mCreatorLength > 0 ) ) { + const std::string creatorString = AVCHD_StringFieldToXMP ( + playlistMark.mAccess.mCreatorLength, playlistMark.mAccess.mCreatorCharacterSet, playlistMark.mAccess.mCreator, 32 ) ; + + if ( ! creatorString.empty() ) { + xmpObj.DeleteProperty ( kXMP_NS_DC, "creator" ); + xmpObj.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, creatorString.c_str() ); + } + } + + if ( playlistMark.mDevice.mPresent && ( playlistMark.mDevice.mSerialNoLength > 0 ) ) { + const std::string serialNoString = AVCHD_StringFieldToXMP ( + playlistMark.mDevice.mSerialNoLength, playlistMark.mDevice.mSerialNoCharacterCode, playlistMark.mDevice.mSerialNo, 24 ) ; + + if ( ! serialNoString.empty() ) xmpObj.SetProperty ( kXMP_NS_EXIF_Aux, "SerialNumber", serialNoString.c_str(), kXMP_DeleteExisting ); + } + + if ( playlistMark.mLocation.mPresent && ( playlistMark.mLocation.mPlaceNameLength > 0 ) ) { + const std::string placeNameString = AVCHD_StringFieldToXMP ( + playlistMark.mLocation.mPlaceNameLength, playlistMark.mLocation.mPlaceNameCharacterSet, playlistMark.mLocation.mPlaceName, 64 ) ; + + if ( ! placeNameString.empty() ) xmpObj.SetProperty ( kXMP_NS_DM, "shotLocation", placeNameString.c_str(), kXMP_DeleteExisting ); + } + } } } // AVCHD_MetaHandler::ProcessXMP @@ -616,7 +2027,8 @@ void AVCHD_MetaHandler::UpdateFile ( bool doSafeUpdate ) std::string xmpPath, tempPath; - this->MakeClipStreamPath ( &xmpPath, ".xmp" ); + bool found = this->MakeClipStreamPath ( &xmpPath, ".xmp", true /* checkFile */ ); + if ( ! found ) XMP_Throw ( "AVCHD_MetaHandler::UpdateFile - XMP is supposed to exist", kXMPErr_InternalFailure ); CreateTempFile ( xmpPath, &tempPath ); LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' ); diff --git a/source/XMPFiles/FileHandlers/AVCHD_Handler.hpp b/source/XMPFiles/FileHandlers/AVCHD_Handler.hpp index 45e9cf9..4123089 100644 --- a/source/XMPFiles/FileHandlers/AVCHD_Handler.hpp +++ b/source/XMPFiles/FileHandlers/AVCHD_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -63,9 +63,11 @@ public: private: AVCHD_MetaHandler() {}; // Hidden on purpose. - - void MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix ); - void MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix ); + + bool MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ) const; + bool MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ) const; + bool MakePlaylistPath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ) const; + void MakeLegacyDigest ( std::string * digestStr ); std::string rootPath, clipName; diff --git a/source/XMPFiles/FileHandlers/AVI_Handler.cpp b/source/XMPFiles/FileHandlers/AVI_Handler.cpp deleted file mode 100644 index 96a11ec..0000000 --- a/source/XMPFiles/FileHandlers/AVI_Handler.cpp +++ /dev/null @@ -1,498 +0,0 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -#include "AVI_Handler.hpp" -#include "RIFF_Support.hpp" - -#if XMP_WinBuild - #include <vfw.h> -#else - #ifndef formtypeAVI - #define formtypeAVI MakeFourCC('A', 'V', 'I', ' ') - #else - #error "formtypeAVI already defined" - #endif -#endif - -using namespace std; - -#define kXMPUserDataType MakeFourCC ( '_', 'P', 'M', 'X' ) /* Yes, backwards! */ - -// FourCC codes for the RIFF chunks -#define aviTimeChunk MakeFourCC('I','S','M','T') -#define avihdrlChunk MakeFourCC('h','d','r','l') -#define myOrgTimeChunk MakeFourCC('t','c','_','O') /* 0x4f5f6374 */ -#define myAltTimeChunk MakeFourCC('t','c','_','A') -#define myOrgReelChunk MakeFourCC('r','n','_','O') /* 0x4f5f6e72 */ -#define myAltReelChunk MakeFourCC('r','n','_','A') -#define myCommentChunk MakeFourCC('c','m','n','t') -#define myTimeList MakeFourCC('T','d','a','t') /* 0x74616454 */ -#define myCommentList MakeFourCC('C','d','a','t') - -#define TIMELEN 18 -#define REELLEN 40 -#define REALTIMELEN 11 -#define COMMENTLEN 256 - -/* list id (4 bytes) + four tags hdrs (8 each) + 2 TIMEs + 2 REELs */ -#define PR_AVI_TIMELEN (12 + 2 * (8 + TIMELEN) + 2 * (8 + REELLEN)) - -#define PR_AVI_COMMENTLEN (12 + 8 + COMMENTLEN) - -#define kStartTimecode "startTimecode" -#define kTimeValue "timeValue" -#define kAltTimecode "altTimecode" -#define kTapeName "tapeName" -#define kAltTapeName "altTapeName" -#define kLogComment "logComment" - -// ================================================================================================= -/// \file AVI_Handler.cpp -/// \brief File format handler for AVI. -/// -/// This header ... -/// -// ================================================================================================= - -// ================================================================================================= -// AVI_MetaHandlerCTor -// ==================== - -XMPFileHandler * AVI_MetaHandlerCTor ( XMPFiles * parent ) -{ - return new AVI_MetaHandler ( parent ); - -} // AVI_MetaHandlerCTor - -// ================================================================================================= -// AVI_CheckFormat -// =============== -// -// An AVI file must begin with "RIFF", a 4 byte little endian length, then "AVI ". The length should -// be fileSize-8, but we don't bother checking this here. - -bool AVI_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef fileRef, - XMPFiles * parent ) -{ - IgnoreParam(format); IgnoreParam(parent); - XMP_Assert ( format == kXMP_AVIFile ); - - if ( fileRef == 0 ) return false; - - enum { kBufferSize = 12 }; - XMP_Uns8 buffer [kBufferSize]; - - LFA_Seek ( fileRef, 0, SEEK_SET ); - LFA_Read ( fileRef, buffer, kBufferSize ); - - // "RIFF" is 52 49 46 46, "AVI " is 41 56 49 20 - if ( (! CheckBytes ( &buffer[0], "\x52\x49\x46\x46", 4 )) || - (! CheckBytes ( &buffer[8], "\x41\x56\x49\x20", 4 )) ) return false; - - return true; - -} // AVI_CheckFormat - -// ================================================================================================= -// AVI_MetaHandler::AVI_MetaHandler -// ================================ - -AVI_MetaHandler::AVI_MetaHandler ( XMPFiles * _parent ) -{ - this->parent = _parent; - this->handlerFlags = kAVI_HandlerFlags; - this->stdCharForm = kXMP_Char8Bit; - -} // AVI_MetaHandler::AVI_MetaHandler - -// ================================================================================================= -// AVI_MetaHandler::~AVI_MetaHandler -// ================================= - -AVI_MetaHandler::~AVI_MetaHandler() -{ - // Nothing to do. - -} // AVI_MetaHandler::~AVI_MetaHandler - -// ================================================================================================= -// AVI_MetaHandler::UpdateFile -// =========================== - -void AVI_MetaHandler::UpdateFile ( bool doSafeUpdate ) -{ - bool ok; - - if ( ! this->needsUpdate ) return; - if ( doSafeUpdate ) XMP_Throw ( "AVI_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); - - XMP_StringPtr packetStr = xmpPacket.c_str(); - XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); - if ( packetLen == 0 ) return; - - // Make sure we're writing an even number of bytes as required by the RIFF specification. - if ( (xmpPacket.size() & 1) == 1 ) xmpPacket.push_back ( ' ' ); - XMP_Assert ( (xmpPacket.size() & 1) == 0 ); - packetStr = xmpPacket.c_str(); // ! Make sure they are current. - packetLen = (XMP_StringLen)xmpPacket.size(); - - LFA_FileRef fileRef(this->parent->fileRef); - if ( fileRef == 0 ) return; - - RIFF_Support::RiffState riffState; - long numTags = RIFF_Support::OpenRIFF ( fileRef, riffState ); - if ( numTags == 0 ) return; - - ok = RIFF_Support::PutChunk ( fileRef, riffState, formtypeAVI, kXMPUserDataType, (char*)packetStr, packetLen ); - if ( ! ok ) return; // If there's an error writing the chunk, bail. - - ok = CreatorAtom::Update ( this->xmpObj, fileRef, formtypeAVI, riffState ); - if ( ! ok ) return; - - // Update legacy metadata - - std::string startTimecodeString, altTimecodeString, orgReelString, altReelString, logCommentString; - - this->xmpObj.GetStructField ( kXMP_NS_DM, kStartTimecode, kXMP_NS_DM, kTimeValue, &startTimecodeString, 0 ); - this->xmpObj.GetStructField ( kXMP_NS_DM, kAltTimecode, kXMP_NS_DM, kTimeValue, &altTimecodeString, 0 ); - this->xmpObj.GetProperty ( kXMP_NS_DM, kTapeName, &orgReelString, 0 ); - this->xmpObj.GetProperty ( kXMP_NS_DM, kAltTapeName, &altReelString, 0 ); - this->xmpObj.GetProperty ( kXMP_NS_DM, kLogComment, &logCommentString, 0 ); - - if ( startTimecodeString.size() != 0 ) { - // I'm not sure why we copy into this 12 char buffer, but this is what Premiere code does. - char aviTime [12]; - memset ( aviTime, 0, 12 ); - memcpy ( aviTime, startTimecodeString.data(), 11 ); // AUDIT: 11 is less than 12 - RewriteChunk ( fileRef, riffState, aviTimeChunk, avihdrlChunk, aviTime ); - } - - if ( logCommentString.size() != 0 ) { - - ok = FindChunk ( riffState, myCommentChunk, myCommentList, 0, 0, 0, 0 ); - - if ( ok ) { - - // Always rewrite the comment string, even if empty, so the user can erase it. - RIFF_Support::RewriteChunk ( fileRef, riffState, myCommentChunk, myCommentList, logCommentString.c_str() ); - - } else { - - ok = MakeChunk ( fileRef, riffState, formtypeAVI, PR_AVI_COMMENTLEN ); - if ( ! ok ) return; // If there's an error making a chunk, bail - - RIFF_Support::ltag listtag; - listtag.id = MakeUns32LE ( FOURCC_LIST ); - listtag.len = MakeUns32LE ( PR_AVI_COMMENTLEN - 8 ); - listtag.subid = MakeUns32LE ( myCommentList ); - LFA_Write ( fileRef, &listtag, 12 ); - - RIFF_Support::WriteChunk ( fileRef, myCommentChunk, logCommentString.c_str(), COMMENTLEN ); - - } - - } - - ok = RIFF_Support::FindChunk ( riffState, myOrgTimeChunk, myTimeList, 0, 0, 0, 0 ); - - if ( ok ) { - - if ( startTimecodeString.size() != 0 ) { - RewriteChunk ( fileRef, riffState, myOrgTimeChunk, myTimeList, startTimecodeString.c_str() ); - } - - if ( altTimecodeString.size() != 0 ) { - RewriteChunk ( fileRef, riffState, myAltTimeChunk, myTimeList, altTimecodeString.c_str() ); - } - - // Always rewrite the reel strings, even if empty, so the user can erase them. - RewriteChunk ( fileRef, riffState, myOrgReelChunk, myTimeList, orgReelString.c_str() ); - RewriteChunk ( fileRef, riffState, myAltReelChunk, myTimeList, altReelString.c_str() ); - - } else { - - // We don't have the legacy part yet. If none of the XMP items exist then don't do anything. - // Otherwise, add all 4 even if empty. This is the original logic from way back. - - bool haveAnyXMP = ( (! startTimecodeString.empty()) || (! altTimecodeString.empty()) || - (! orgReelString.empty()) || (! altReelString.empty()) ); - - if ( haveAnyXMP ) { - - ok = MakeChunk ( fileRef, riffState, formtypeAVI, PR_AVI_TIMELEN ); - if ( ! ok ) return; // If there's an error making a chunk, bail - - RIFF_Support::ltag listtag; - listtag.id = MakeUns32LE ( FOURCC_LIST ); - listtag.len = MakeUns32LE ( PR_AVI_TIMELEN - 8 ); - listtag.subid = MakeUns32LE ( myTimeList ); - LFA_Write(fileRef, &listtag, 12); - - RIFF_Support::WriteChunk ( fileRef, myOrgTimeChunk, startTimecodeString.c_str(), TIMELEN ); - RIFF_Support::WriteChunk ( fileRef, myAltTimeChunk, altTimecodeString.c_str(), TIMELEN ); - RIFF_Support::WriteChunk ( fileRef, myOrgReelChunk, orgReelString.c_str(), REELLEN ); - RIFF_Support::WriteChunk ( fileRef, myAltReelChunk, altReelString.c_str(), REELLEN ); - - } - - } - - this->needsUpdate = false; - -} // AVI_MetaHandler::UpdateFile - -// ================================================================================================= -// AVI_MetaHandler::WriteFile -// ========================== - -void AVI_MetaHandler::WriteFile ( LFA_FileRef sourceRef, - const std::string & sourcePath ) -{ - IgnoreParam(sourceRef); IgnoreParam(sourcePath); - - XMP_Throw ( "AVI_MetaHandler::WriteFile: Not supported", kXMPErr_Unavailable ); - -} // AVI_MetaHandler::WriteFile - -// ================================================================================================= - -static void StripSimpleEmpty ( SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr prop ) -{ - // Small hack to clean up bad data. There are cases of code writing xmpDM:startTimecode and - // xmpDM:altTimecode as simple properties with empty values. They are supposed to be structs. - - std::string value; - XMP_OptionBits flags; - - bool found = xmp->GetProperty ( ns, prop, &value, &flags ); - - if ( found && XMP_PropIsSimple(flags) && value.empty() ) { - xmp->DeleteProperty ( ns, prop ); - } - -} - -// ================================================================================================= -// AVI_MetaHandler::CacheFileData -// ============================== - -void AVI_MetaHandler::CacheFileData() -{ - bool ok; - - this->containsXMP = false; - - LFA_FileRef fileRef ( this->parent->fileRef ); //*** simplify to assignment - if ( fileRef == 0 ) return; - - bool updateFile = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); - if ( updateFile ) { - - // Workaround for bad files in the field that have a bad size in the outermost RIFF chunk. - // Repair the cases where the length is too long (beyond EOF). Don't repair a length that is - // less than EOF, we don't know if there actually are multiple top level chunks. There is - // also a check and "runtime repair" inside ReadTag, needed for read-only file access. - - XMP_Int64 fileLen = LFA_Measure ( fileRef ); - XMP_Uns32 riffLen; - - LFA_Seek ( fileRef, 4, SEEK_SET ); - LFA_Read ( fileRef, &riffLen, 4 ); - riffLen = GetUns32LE ( &riffLen ); - - if ( (fileLen >= 8) && ((XMP_Int64)riffLen > (fileLen - 8)) ) { // Is the initial chunk too long? - - bool repairFile = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenRepairFile ); - if ( ! repairFile ) { - XMP_Throw ( "Initial RIFF tag exceeds file length", kXMPErr_BadValue ); - } else { - riffLen = MakeUns32LE ( (XMP_Uns32)fileLen - 8 ); - LFA_Seek ( fileRef, 4, SEEK_SET ); - LFA_Write ( fileRef, &riffLen, 4 ); - } - - } - - } - - // Contnue with normal processing. - - RIFF_Support::RiffState riffState; - long numTags = RIFF_Support::OpenRIFF ( fileRef, riffState ); - if ( numTags == 0 ) return; //*** shouldn't we throw ? XMP_Throw("invalid file format") or such? - - // Determine the size of the metadata - unsigned long bufferSize(0); - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType /* _PMX, the xmp packet */, 0, 0, 0, &bufferSize); - - if ( ! ok ) { - - packetInfo.writeable = true; // If no packet found, created packets will be writeable. - - } else if ( bufferSize > 0 ) { - - // Size and clear the buffer - this->xmpPacket.reserve ( bufferSize ); - this->xmpPacket.assign ( bufferSize, ' ' ); - - // Get the metadata - XMP_Uns64 xmpPacketPosition; - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType /* _PMX, the xmp packet */, 0, 0, - (char*)this->xmpPacket.c_str(), &bufferSize, &xmpPacketPosition ); - if ( ok ) { - this->packetInfo.offset = xmpPacketPosition; - this->packetInfo.length = bufferSize; - this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); - this->containsXMP = true; - } - - } - - // Reconcile legacy metadata. - - std::string aviTimeString, orgTimeString, altTimeString, projectPathString; - unsigned long aviTimeSize, orgTimeSize, altTimeSize; - - StripSimpleEmpty ( &this->xmpObj, kXMP_NS_DM, kStartTimecode ); - StripSimpleEmpty ( &this->xmpObj, kXMP_NS_DM, kAltTimecode ); - - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, aviTimeChunk, avihdrlChunk, 0, 0, &aviTimeSize ); - if ( ok ) { - aviTimeString.reserve ( aviTimeSize ); - aviTimeString.assign ( aviTimeSize, ' ' ); - RIFF_Support::GetRIFFChunk ( fileRef, riffState, aviTimeChunk, avihdrlChunk, 0, (char*)aviTimeString.c_str(), &aviTimeSize ); - } - - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myOrgTimeChunk, myTimeList, 0, 0, &orgTimeSize ); - if ( ok ) { - orgTimeString.reserve ( orgTimeSize ); - orgTimeString.assign ( orgTimeSize, ' ' ); - RIFF_Support::GetRIFFChunk ( fileRef, riffState, myOrgTimeChunk, myTimeList, 0, (char*)orgTimeString.c_str(), &orgTimeSize ); - } - - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myAltTimeChunk, myTimeList, 0, 0, &altTimeSize ); - if ( ok ) { - altTimeString.reserve ( altTimeSize ); - altTimeString.assign ( altTimeSize, ' ' ); - RIFF_Support::GetRIFFChunk ( fileRef, riffState, myAltTimeChunk, myTimeList, 0, (char*)altTimeString.c_str(), &altTimeSize ); - } - - if ( (! aviTimeString.empty()) && orgTimeString.empty() && (altTimeString.empty()) ) { - - // If we have an avi time, and not the org or alt, use the avi. I suspect this is for some earlier backwards compatibility. - - std::string xmpString; - this->xmpObj.GetStructField ( kXMP_NS_DM, kStartTimecode, kXMP_NS_DM, kTimeValue, &xmpString, 0 ); - if ( xmpString.compare ( 0, REALTIMELEN, aviTimeString, 0, REALTIMELEN ) ) { - this->xmpObj.SetStructField ( kXMP_NS_DM, kStartTimecode, kXMP_NS_DM, kTimeValue, aviTimeString, 0 ); - this->containsXMP = true; - } - - } else { - - // Otherwise, check the original and alt timecodes. - - if ( ! orgTimeString.empty() ) { - std::string xmpString; - this->xmpObj.GetStructField ( kXMP_NS_DM, kStartTimecode, kXMP_NS_DM, kTimeValue, &xmpString, 0 ); - if (xmpString.compare ( 0, REALTIMELEN, orgTimeString, 0, REALTIMELEN ) ) { - this->xmpObj.SetStructField ( kXMP_NS_DM, kStartTimecode, kXMP_NS_DM, kTimeValue, orgTimeString, 0 ); - this->containsXMP = true; - } - } - - if ( ! altTimeString.empty() ) { - std::string xmpString; - this->xmpObj.GetStructField ( kXMP_NS_DM, kAltTimecode, kXMP_NS_DM, kTimeValue, &xmpString, 0 ); - if ( xmpString.compare ( 0, REALTIMELEN, altTimeString, 0, REALTIMELEN ) ) { - this->xmpObj.SetStructField ( kXMP_NS_DM, kAltTimecode, kXMP_NS_DM, kTimeValue, altTimeString, 0 ); - this->containsXMP = true; - } - } - - } - - unsigned long orgReelSize; - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myOrgReelChunk, myTimeList, 0, 0, &orgReelSize ); - if ( ok ) { - - std::string orgReelString; - orgReelString.reserve ( orgReelSize ); - orgReelString.assign ( orgReelSize, ' ' ); - RIFF_Support::GetRIFFChunk ( fileRef, riffState, myOrgReelChunk, myTimeList, 0, (char*)orgReelString.c_str(), &orgReelSize ); - - if ( ! orgReelString.empty() ) { - std::string xmpString; - this->xmpObj.GetProperty ( kXMP_NS_DM, kTapeName, &xmpString, 0 ); - if ( xmpString.compare ( 0, REELLEN, orgReelString, 0, REELLEN ) ) { - this->xmpObj.SetProperty ( kXMP_NS_DM, kTapeName, orgReelString, 0 ); - this->containsXMP = true; - } - } - - } - - unsigned long altReelSize; - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myAltReelChunk, myTimeList, 0, 0, &altReelSize ); - if ( ok ) { - - std::string altReelString; - altReelString.reserve ( altReelSize ); - altReelString.assign ( altReelSize, ' ' ); - RIFF_Support::GetRIFFChunk ( fileRef, riffState, myAltReelChunk, myTimeList, 0, (char*)altReelString.c_str(), &altReelSize ); - - if ( ! altReelString.empty() ) { - std::string xmpString; - this->xmpObj.GetProperty ( kXMP_NS_DM, kAltTapeName, &xmpString, 0 ); - if ( xmpString.compare ( 0, REELLEN, altReelString, 0, REELLEN ) ) { - this->xmpObj.SetProperty ( kXMP_NS_DM, kAltTapeName, altReelString, 0 ); - this->containsXMP = true; - } - } - - } - - unsigned long logCommentSize; - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCommentChunk, myCommentList, 0, 0, &logCommentSize ); - if ( ok ) { - - std::string logCommentString; - logCommentString.reserve ( logCommentSize ); - logCommentString.assign ( logCommentSize, ' ' ); - RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCommentChunk, myCommentList, 0, (char*)logCommentString.c_str(), &logCommentSize ); - - if ( ! logCommentString.empty() ) { - std::string xmpString; - this->xmpObj.GetProperty ( kXMP_NS_DM, kLogComment, &xmpString, 0 ); - if ( xmpString.compare ( logCommentString ) ) { - this->xmpObj.SetProperty ( kXMP_NS_DM, kLogComment, logCommentString, 0 ); - this->containsXMP = true; - } - } - - } - - CreatorAtom::Import ( this->xmpObj, fileRef, riffState ); - - //// Update the xmpPacket, as the xmpObj might have been updated with legacy info. - //// Produce packet of same size [1781657] - //try { - // this->xmpObj.SerializeToBuffer ( &this->xmpPacket, - // (kXMP_UseCompactFormat | kXMP_ExactPacketLength) , packetInfo.length ); - //} catch ( XMP_Error ) { - // this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat ) ); - //} - - // removed for [1781657] this->packetInfo.offset = kXMPFiles_UnknownOffset; - // removed for [1781657] this->packetInfo.length = (XMP_StringLen)this->xmpPacket.size(); - this->processedXMP = this->containsXMP; - -} // AVI_MetaHandler::CacheFileData diff --git a/source/XMPFiles/FileHandlers/AVI_Handler.hpp b/source/XMPFiles/FileHandlers/AVI_Handler.hpp deleted file mode 100644 index 9db656c..0000000 --- a/source/XMPFiles/FileHandlers/AVI_Handler.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef __AVI_Handler_hpp__ -#define __AVI_Handler_hpp__ 1 - -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -#include "XMPFiles_Impl.hpp" - -// ================================================================================================= -/// \file AVI_Handler.hpp -/// \brief File format handler for AVI. -/// -/// This header ... -/// -// ================================================================================================= - -extern XMPFileHandler * AVI_MetaHandlerCTor ( XMPFiles * parent ); - -extern bool AVI_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef fileRef, - XMPFiles * parent ); - -static const XMP_OptionBits kAVI_HandlerFlags = (kXMPFiles_CanInjectXMP | - kXMPFiles_CanExpand | - kXMPFiles_PrefersInPlace | - kXMPFiles_AllowsOnlyXMP | - kXMPFiles_ReturnsRawPacket); - // In the future, we'll add kXMPFiles_CanReconcile - -class AVI_MetaHandler : public XMPFileHandler -{ -public: - - AVI_MetaHandler ( XMPFiles * parent ); - ~AVI_MetaHandler(); - - void CacheFileData(); - - void UpdateFile ( bool doSafeUpdate ); - void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); - -}; // AVI_MetaHandler - -// ================================================================================================= - -#endif /* __AVI_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/Basic_Handler.cpp b/source/XMPFiles/FileHandlers/Basic_Handler.cpp index f3b471e..3efd739 100644 --- a/source/XMPFiles/FileHandlers/Basic_Handler.cpp +++ b/source/XMPFiles/FileHandlers/Basic_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/Basic_Handler.hpp b/source/XMPFiles/FileHandlers/Basic_Handler.hpp index 45eef9f..17b9775 100644 --- a/source/XMPFiles/FileHandlers/Basic_Handler.hpp +++ b/source/XMPFiles/FileHandlers/Basic_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/FLV_Handler.cpp b/source/XMPFiles/FileHandlers/FLV_Handler.cpp index 2472870..f9624ed 100644 --- a/source/XMPFiles/FileHandlers/FLV_Handler.cpp +++ b/source/XMPFiles/FileHandlers/FLV_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -316,7 +316,7 @@ static inline bool CheckName ( XMP_StringPtr inputName, XMP_Uns16 inputLen, void FLV_MetaHandler::CacheFileData() { - XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + XMP_Assert ( ! this->containsXMP ); XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; @@ -569,6 +569,7 @@ void FLV_MetaHandler::UpdateFile ( bool doSafeUpdate ) this->WriteFile ( origRef, origPath ); } catch ( ... ) { LFA_Close ( updateRef ); + LFA_Delete ( updatePath.c_str() ); this->parent->filePath = origPath; this->parent->fileRef = origRef; throw; diff --git a/source/XMPFiles/FileHandlers/FLV_Handler.hpp b/source/XMPFiles/FileHandlers/FLV_Handler.hpp index 1fea76d..80ed1a0 100644 --- a/source/XMPFiles/FileHandlers/FLV_Handler.hpp +++ b/source/XMPFiles/FileHandlers/FLV_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/InDesign_Handler.cpp b/source/XMPFiles/FileHandlers/InDesign_Handler.cpp index c26db20..2f9845e 100644 --- a/source/XMPFiles/FileHandlers/InDesign_Handler.cpp +++ b/source/XMPFiles/FileHandlers/InDesign_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/InDesign_Handler.hpp b/source/XMPFiles/FileHandlers/InDesign_Handler.hpp index bed1851..aded52e 100644 --- a/source/XMPFiles/FileHandlers/InDesign_Handler.hpp +++ b/source/XMPFiles/FileHandlers/InDesign_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/JPEG_Handler.cpp b/source/XMPFiles/FileHandlers/JPEG_Handler.cpp index 6917355..a3842f8 100644 --- a/source/XMPFiles/FileHandlers/JPEG_Handler.cpp +++ b/source/XMPFiles/FileHandlers/JPEG_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -13,6 +13,7 @@ #include "PSIR_Support.hpp" #include "IPTC_Support.hpp" #include "ReconcileLegacy.hpp" +#include "Reconcile_Impl.hpp" #include "MD5.h" @@ -88,23 +89,23 @@ bool JPEG_CheckFormat ( XMP_FileFormat format, XMP_Assert ( format == kXMP_JPEGFile ); IOBuffer ioBuf; - + LFA_Seek ( fileRef, 0, SEEK_SET ); if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; // We need at least 4, the buffer is filled anyway. - + // First look for the SOI standalone marker. Then skip all 0xFF bytes, padding plus the high // order byte of the next marker. Finally see if the next marker is legit. - + if ( ! CheckBytes ( ioBuf.ptr, "\xFF\xD8", 2 ) ) return false; ioBuf.ptr += 2; // Move past the SOI. while ( (ioBuf.ptr < ioBuf.limit) && (*ioBuf.ptr == 0xFF) ) ++ioBuf.ptr; if ( ioBuf.ptr == ioBuf.limit ) return false; - + XMP_Uns8 id = *ioBuf.ptr; if ( id >= 0xDD ) return true; // The most probable cases. if ( (id < 0xC0) || ((id & 0xF8) == 0xD0) || (id == 0xD8) || (id == 0xDA) || (id == 0xDC) ) return false; return true; - + } // JPEG_CheckFormat // ================================================================================================= @@ -130,44 +131,8 @@ JPEG_MetaHandler::~JPEG_MetaHandler() if ( exifMgr != 0 ) delete ( exifMgr ); if ( psirMgr != 0 ) delete ( psirMgr ); if ( iptcMgr != 0 ) delete ( iptcMgr ); - -} // JPEG_MetaHandler::~JPEG_MetaHandler - -// ================================================================================================= -// TableOrDataMarker -// ================= -// -// Returns true if the marker is for a table or data marker segment: -// FFC4 - DHT -// FFCC - DAC -// FFDB - DQT -// FFDC - DNL -// FFDD - DRI -// FFDE - DHP -// FFDF - EXP -// FFEn - APPn -// FFFE - COM - -static inline bool TableOrDataMarker ( XMP_Uns16 marker ) -{ - - if ( (marker & 0xFFF0) == 0xFFE0 ) return true; // APPn is probably the most common case. - - if ( marker < 0xFFC4 ) return false; - if ( marker == 0xFFC4 ) return true; - if ( marker < 0xFFCC ) return false; - if ( marker == 0xFFCC ) return true; - - if ( marker < 0xFFDB ) return false; - if ( marker <= 0xFFDF ) return true; - - if ( marker < 0xFFFE ) return false; - if ( marker == 0xFFFE ) return true; - - return false; - -} // TableOrDataMarker +} // JPEG_MetaHandler::~JPEG_MetaHandler // ================================================================================================= // JPEG_MetaHandler::CacheFileData @@ -182,7 +147,7 @@ static inline bool TableOrDataMarker ( XMP_Uns16 marker ) // EOI marker, 2 bytes, 0xFFD9 // // Each marker segment begins with a 2 byte big endian marker and a 2 byte big endian length. The -// length includes the 2 bytes of the length field but not the marker. The high order byte of a +// length includes the 2 bytes of the length field but not the marker. The high order byte of a // marker is 0xFF, the low order byte tells what kind of marker. A marker can be preceeded by any // number of 0xFF fill bytes, however there are no alignment constraints. // @@ -221,20 +186,20 @@ void JPEG_MetaHandler::CacheFileData() size_t segLen; bool ok; IOBuffer ioBuf; - + XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); - + ExtendedXMPInfo extXMP; - - XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + + XMP_Assert ( ! this->containsXMP ); // Set containsXMP to true here only if the standard XMP packet is found. - + XMP_Assert ( kPSIRSignatureLength == (strlen(kPSIRSignatureString) + 1) ); XMP_Assert ( kMainXMPSignatureLength == (strlen(kMainXMPSignatureString) + 1) ); XMP_Assert ( kExtXMPSignatureLength == (strlen(kExtXMPSignatureString) + 1) ); - + // ------------------------------------------------------------------------------------------- // Look for any of the Exif, PSIR, main XMP, or extended XMP marker segments. Quit when we hit // an SOFn, EOI, or invalid/unexpected marker. @@ -242,13 +207,13 @@ void JPEG_MetaHandler::CacheFileData() LFA_Seek ( fileRef, 2, SEEK_SET ); // Skip the SOI. The JPEG header has already been verified. ioBuf.filePos = 2; RefillBuffer ( fileRef, &ioBuf ); - + while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "JPEG_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); } - + if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return; if ( *ioBuf.ptr != 0xFF ) return; // All valid markers have a high byte of 0xFF. @@ -256,26 +221,31 @@ void JPEG_MetaHandler::CacheFileData() ++ioBuf.ptr; if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return; } - + XMP_Uns16 marker = 0xFF00 + *ioBuf.ptr; + + if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) break; // Quit reading at the first SOS marker or at EOI. + if ( (marker == 0xFF01) || // Ill-formed file if we encounter a TEM or RSTn marker. + ((0xFFD0 <= marker) && (marker <= 0xFFD7)) ) return; + if ( marker == 0xFFED ) { - + // This is an APP13 marker, is it the Photoshop image resources? - + ++ioBuf.ptr; // Move ioBuf.ptr to the marker segment length field. if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return; - + segLen = GetUns16BE ( ioBuf.ptr ); if ( segLen < 2 ) return; // Invalid JPEG. ioBuf.ptr += 2; // Move ioBuf.ptr to the marker segment content. segLen -= 2; // Adjust segLen to count just the content portion. - + ok = CheckFileSpace ( fileRef, &ioBuf, kPSIRSignatureLength ); if ( ok && (segLen >= kPSIRSignatureLength) && CheckBytes ( ioBuf.ptr, kPSIRSignatureString, kPSIRSignatureLength ) ) { - + // This is the Photoshop image resources, cache the contents. ioBuf.ptr += kPSIRSignatureLength; // Move ioBuf.ptr to the image resources. @@ -285,9 +255,9 @@ void JPEG_MetaHandler::CacheFileData() this->psirContents.assign ( (XMP_StringPtr)ioBuf.ptr, segLen ); ioBuf.ptr += segLen; - + } else { - + // This is the not Photoshop image resources, skip the marker segment's content. if ( segLen <= size_t(ioBuf.limit - ioBuf.ptr) ) { @@ -300,30 +270,30 @@ void JPEG_MetaHandler::CacheFileData() } } - + continue; // Move on to the next marker. - + } else if ( marker == 0xFFE1 ) { - + // This is an APP1 marker, is it the Exif, main XMP, or extended XMP? // ! Check in that order, which happens to be increasing signature string length. - + ++ioBuf.ptr; // Move ioBuf.ptr to the marker segment length field. if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return; - + segLen = GetUns16BE ( ioBuf.ptr ); if ( segLen < 2 ) return; // Invalid JPEG. ioBuf.ptr += 2; // Move ioBuf.ptr to the marker segment content. segLen -= 2; // Adjust segLen to count just the content portion. - + // Check for the Exif APP1 marker segment. - + ok = CheckFileSpace ( fileRef, &ioBuf, kExifSignatureLength ); if ( ok && (segLen >= kExifSignatureLength) && (CheckBytes ( ioBuf.ptr, kExifSignatureString, kExifSignatureLength ) || CheckBytes ( ioBuf.ptr, kExifSignatureAltStr, kExifSignatureLength )) ) { - + // This is the Exif metadata, cache the contents. ioBuf.ptr += kExifSignatureLength; // Move ioBuf.ptr to the TIFF stream. @@ -333,24 +303,24 @@ void JPEG_MetaHandler::CacheFileData() this->exifContents.assign ( (XMP_StringPtr)ioBuf.ptr, segLen ); ioBuf.ptr += segLen; - + continue; // Move on to the next marker. - + } - + // Check for the main XMP APP1 marker segment. - + ok = CheckFileSpace ( fileRef, &ioBuf, kMainXMPSignatureLength ); if ( ok && (segLen >= kMainXMPSignatureLength) && CheckBytes ( ioBuf.ptr, kMainXMPSignatureString, kMainXMPSignatureLength ) ) { - + // This is the main XMP, cache the contents. ioBuf.ptr += kMainXMPSignatureLength; // Move ioBuf.ptr to the XMP Packet. segLen -= kMainXMPSignatureLength; // Adjust segLen to count just the XMP Packet. ok = CheckFileSpace ( fileRef, &ioBuf, segLen ); // Buffer the full content portion. if ( ! ok ) return; // Must be a truncated file. - + this->packetInfo.offset = ioBuf.filePos + (ioBuf.ptr - &ioBuf.data[0]); this->packetInfo.length = (XMP_Int32)segLen; this->packetInfo.padSize = 0; // Assume for now, set these properly in ProcessXMP. @@ -359,50 +329,50 @@ void JPEG_MetaHandler::CacheFileData() this->xmpPacket.assign ( (XMP_StringPtr)ioBuf.ptr, segLen ); ioBuf.ptr += segLen; // ! Set this->packetInfo.offset first! - + this->containsXMP = true; // Found the standard XMP packet. continue; // Move on to the next marker. - + } - + // Check for an extension XMP APP1 marker segment. - + ok = CheckFileSpace ( fileRef, &ioBuf, kExtXMPPrefixLength ); // ! The signature, GUID, length, and offset. if ( ok && (segLen >= kExtXMPPrefixLength) && CheckBytes ( ioBuf.ptr, kExtXMPSignatureString, kExtXMPSignatureLength ) ) { - + // This is a portion of the extended XMP, cache the contents. This is complicated by // the need to tolerate files where the extension portions are not in order. The // local ExtendedXMPInfo map uses the GUID as the key and maps that to a struct that // has the full length and a map of the known portions. This known portion map uses // the offset of the portion as the key and maps that to a string. Only fully seen // extended XMP streams are kept, the right one gets picked in ProcessXMP. - + segLen -= kExtXMPPrefixLength; // Adjust segLen to count just the XMP stream portion. ioBuf.ptr += kExtXMPSignatureLength; // Move ioBuf.ptr to the GUID. GUID_32 guid; XMP_Assert ( sizeof(guid.data) == 32 ); memcpy ( &guid.data[0], ioBuf.ptr, sizeof(guid.data) ); // AUDIT: Use of sizeof(guid.data) is safe. - + ioBuf.ptr += 32; // Move ioBuf.ptr to the length and offset. XMP_Uns32 fullLen = GetUns32BE ( ioBuf.ptr ); XMP_Uns32 offset = GetUns32BE ( ioBuf.ptr+4 ); - + ioBuf.ptr += 8; // Move ioBuf.ptr to the XMP stream portion. - + #if Trace_UnlimitedJPEG printf ( "New extended XMP portion: fullLen %d, offset %d, GUID %.32s\n", fullLen, offset, guid.data ); #endif - + // Find the ExtXMPContent for this GUID, and the string for this portion's offset. - + ExtendedXMPInfo::iterator guidPos = extXMP.find ( guid ); if ( guidPos == extXMP.end() ) { ExtXMPContent newExtContent ( fullLen ); guidPos = extXMP.insert ( extXMP.begin(), ExtendedXMPInfo::value_type ( guid, newExtContent ) ); } - + ExtXMPPortions::iterator offsetPos; ExtXMPContent & extContent = guidPos->second; @@ -427,21 +397,21 @@ void JPEG_MetaHandler::CacheFileData() ExtXMPPortions::value_type ( offset, std::string() ) ); } } - + // Cache this portion of the extended XMP. - + std::string & extPortion = offsetPos->second; ok = CheckFileSpace ( fileRef, &ioBuf, segLen ); // Buffer the full content portion. if ( ! ok ) return; // Must be a truncated file. extPortion.append ( (XMP_StringPtr)ioBuf.ptr, segLen ); ioBuf.ptr += segLen; - + continue; // Move on to the next marker. - + } - + // If we get here this is some other uninteresting APP1 marker segment, skip it. - + if ( segLen <= size_t(ioBuf.limit - ioBuf.ptr) ) { ioBuf.ptr += segLen; // The next marker is in this buffer. } else { @@ -450,14 +420,14 @@ void JPEG_MetaHandler::CacheFileData() ioBuf.filePos = LFA_Seek ( fileRef, skipCount, SEEK_CUR ); ioBuf.ptr = ioBuf.limit; // No data left in the buffer. } - - } else if ( TableOrDataMarker ( marker ) ) { - + + } 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. @@ -469,39 +439,35 @@ void JPEG_MetaHandler::CacheFileData() ioBuf.filePos = LFA_Seek ( fileRef, skipCount, SEEK_CUR ); ioBuf.ptr = ioBuf.limit; // No data left in the buffer. } - - continue; // Move on to the next marker. - } else { + continue; // Move on to the next marker. - break; // This is a terminating marker of some sort. - } - + } if ( ! extXMP.empty() ) { - + // We have extended XMP. Find out which ones are complete, collapse them into a single // string, and save them for ProcessXMP. - + ExtendedXMPInfo::iterator guidPos = extXMP.begin(); ExtendedXMPInfo::iterator guidEnd = extXMP.end(); - + for ( ; guidPos != guidEnd; ++guidPos ) { - + ExtXMPContent & thisContent = guidPos->second; ExtXMPPortions::iterator partZero = thisContent.portions.begin(); ExtXMPPortions::iterator partEnd = thisContent.portions.end(); ExtXMPPortions::iterator partPos = partZero; - + #if Trace_UnlimitedJPEG printf ( "Extended XMP portions for GUID %.32s, full length %d\n", guidPos->first.data, guidPos->second.length ); printf ( " Offset %d, length %d, next offset %d\n", partZero->first, partZero->second.size(), (partZero->first + partZero->second.size()) ); #endif - + for ( ++partPos; partPos != partEnd; ++partPos ) { #if Trace_UnlimitedJPEG printf ( " Offset %d, length %d, next offset %d\n", @@ -510,7 +476,7 @@ void JPEG_MetaHandler::CacheFileData() if ( partPos->first != partZero->second.size() ) break; // Quit if not contiguous. partZero->second.append ( partPos->second ); } - + if ( (partPos == partEnd) && (partZero->first == 0) && (partZero->second.size() == thisContent.length) ) { // This is a complete extended XMP stream. this->extendedXMP.insert ( ExtendedXMPMap::value_type ( guidPos->first, partZero->second ) ); @@ -519,38 +485,78 @@ void JPEG_MetaHandler::CacheFileData() guidPos->first.data, partZero->second.size() ); #endif } - + } - + } - + } // JPEG_MetaHandler::CacheFileData // ================================================================================================= -// JPEG_MetaHandler::ProcessTNail -// ============================== +// TrimFullExifAPP1 +// ================ +// +// Try to trim trailing padding from full Exif APP1 segment written by some Nikon cameras. Do a +// temporary read-only parse of the Exif APP1 contents, determine the highest used offset, trim the +// padding if all zero bytes. -void JPEG_MetaHandler::ProcessTNail() +static const char * IFDNames[] = { "Primary", "TNail", "Exif", "GPS", "Interop", }; + +static void TrimFullExifAPP1 ( std::string * exifContents ) { + TIFF_MemoryReader tempMgr; + TIFF_MemoryReader::TagInfo tagInfo; + bool tagFound, isNikon; + + // ! Make a copy of the data to parse! The RO memory TIFF manager will flip bytes in-place! + tempMgr.ParseMemoryStream ( exifContents->data(), (XMP_Uns32)exifContents->size(), true /* copy data */ ); + + // Only trim the Exif APP1 from Nikon cameras. + tagFound = tempMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_Make, &tagInfo ); + isNikon = tagFound && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count >= 5) && + (memcmp ( tagInfo.dataPtr, "NIKON", 5) == 0); + if ( ! isNikon ) return; - XMP_Assert ( ! this->processedTNail ); - this->processedTNail = true; // Make sure we only come through here once. - this->containsTNail = false; // Set it to true after all of the info is gathered. + // Determine the highest used offset (actually 1 beyond that). Look at the IFD structure, and + // the thumbnail info. Ignore the MakerNote tag, Nikon says they are self-contained. - if ( this->exifMgr == 0 ) { // Thumbnails only need the Exif, not the PSIR or IPTC. - bool readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0); - if ( readOnly ) { // *** Could reduce heap usage by not copying in TIFF_MemoryReader. - this->exifMgr = new TIFF_MemoryReader(); - } else { - this->exifMgr = new TIFF_FileWriter(); + XMP_Uns32 maxOffset = 0; + + for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + TIFF_MemoryReader::TagInfoMap tagMap; + bool ifdFound = tempMgr.GetIFD ( ifd, &tagMap ); + if ( ! ifdFound ) continue; + + TIFF_MemoryReader::TagInfoMap::const_iterator mapPos = tagMap.begin(); + TIFF_MemoryReader::TagInfoMap::const_iterator mapEnd = tagMap.end(); + + for ( ; mapPos != mapEnd; ++mapPos ) { + const TIFF_MemoryReader::TagInfo & tagInfo = mapPos->second; + XMP_Uns32 tagEnd = tempMgr.GetValueOffset ( ifd, tagInfo.id ) + tagInfo.dataLen; + if ( tagEnd > maxOffset ) maxOffset = tagEnd; } - this->exifMgr->ParseMemoryStream ( this->exifContents.c_str(), (XMP_Uns32)this->exifContents.size() ); + } - this->containsTNail = this->exifMgr->GetTNailInfo ( &this->tnailInfo ); - if ( this->containsTNail ) this->tnailInfo.fileFormat = this->parent->format; + tagFound = tempMgr.GetTag ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat, &tagInfo ); + if ( tagFound ) { + XMP_Uns32 tnailOffset = tempMgr.GetUns32 ( tagInfo.dataPtr ); + tagFound = tempMgr.GetTag ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormatLength, &tagInfo ); + if ( ! tagFound ) return; // Don't trim if there is a TNail offset but no length. + tnailOffset += tempMgr.GetUns32 ( tagInfo.dataPtr ); + if ( tnailOffset > maxOffset ) maxOffset = tnailOffset; + } -} // JPEG_MetaHandler::ProcessTNail + if ( maxOffset >= exifContents->size() ) return; // Sanity check for in bounds maximum offset. + + for ( size_t i = maxOffset, limit = exifContents->size(); i < limit; ++i ) { + if ( (*exifContents)[i] != 0 ) return; // Don't trim if unless the trailer is all zero. + } + + exifContents->erase ( maxOffset ); + +} // TrimFullExifAPP1 // ================================================================================================= // JPEG_MetaHandler::ProcessXMP @@ -560,12 +566,12 @@ void JPEG_MetaHandler::ProcessTNail() void JPEG_MetaHandler::ProcessXMP() { - + XMP_Assert ( ! this->processedXMP ); this->processedXMP = true; // Make sure we only come through here once. - + // Create the PSIR and IPTC handlers, even if there is no legacy. They might be needed for updates. - + XMP_Assert ( (this->psirMgr == 0) && (this->iptcMgr == 0) ); // ProcessTNail might create the exifMgr. bool readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0); @@ -575,75 +581,60 @@ void JPEG_MetaHandler::ProcessXMP() this->psirMgr = new PSIR_MemoryReader(); this->iptcMgr = new IPTC_Reader(); // ! Parse it later. } else { + if ( this->exifContents.size() == (65534 - 2 - 6) ) TrimFullExifAPP1 ( &this->exifContents ); if ( this->exifMgr == 0 ) this->exifMgr = new TIFF_FileWriter(); this->psirMgr = new PSIR_FileWriter(); - #if ! XMP_UNIXBuild - this->iptcMgr = new IPTC_Writer(); // ! Parse it later. - #else - // ! Hack until the legacy-as-local issues are resolved for generic UNIX. - this->iptcMgr = new IPTC_Reader(); // ! Import IPTC but don't export it. - #endif + this->iptcMgr = new IPTC_Writer(); // ! Parse it later. } // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy // import if the XMP packet gets parsing errors. - bool found; - bool haveExif = (! this->exifContents.empty()); - bool haveIPTC = false; - - RecJTP_LegacyPriority lastLegacy = kLegacyJTP_None; - TIFF_Manager & exif = *this->exifMgr; // Give the compiler help in recognizing non-aliases. PSIR_Manager & psir = *this->psirMgr; IPTC_Manager & iptc = *this->iptcMgr; + bool haveExif = (! this->exifContents.empty()); if ( haveExif ) { exif.ParseMemoryStream ( this->exifContents.c_str(), (XMP_Uns32)this->exifContents.size() ); } - - if ( ! this->psirContents.empty() ) { + + bool havePSIR = (! this->psirContents.empty()); + if ( havePSIR ) { psir.ParseMemoryResources ( this->psirContents.c_str(), (XMP_Uns32)this->psirContents.size() ); } - - // Determine the last-legacy priority and do the reconciliation. For JPEG files, the relevant - // legacy priorities (ignoring Mac pnot and ANPA resources) are: - // kLegacyJTP_PSIR_OldCaption - highest - // kLegacyJTP_PSIR_IPTC - // kLegacyJTP_JPEG_TIFF_Tags - // kLegacyJTP_None - lowest - - found = psir.GetImgRsrc ( kPSIR_OldCaption, 0 ); - if ( ! found ) found = psir.GetImgRsrc ( kPSIR_OldCaptionPStr, 0 ); - if ( found ) { - haveIPTC = true; - lastLegacy = kLegacyJTP_PSIR_OldCaption; - } PSIR_Manager::ImgRsrcInfo iptcInfo; - found = psir.GetImgRsrc ( kPSIR_IPTC, &iptcInfo ); - if ( found ) { - haveIPTC = true; - iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); - if ( lastLegacy < kLegacyJTP_PSIR_IPTC ) lastLegacy = kLegacyJTP_PSIR_IPTC; - } + bool haveIPTC = false; + if ( havePSIR ) haveIPTC = psir.GetImgRsrc ( kPSIR_IPTC, &iptcInfo );; + int iptcDigestState = kDigestMatches; - if ( lastLegacy < kLegacyJTP_JPEG_TIFF_Tags ) { - found = exif.GetTag ( kTIFF_PrimaryIFD, kTIFF_ImageDescription, 0 ); - if ( ! found ) found = exif.GetTag ( kTIFF_PrimaryIFD, kTIFF_Artist, 0 ); - if ( ! found ) found = exif.GetTag ( kTIFF_PrimaryIFD, kTIFF_Copyright, 0 ); - if ( found ) lastLegacy = kLegacyJTP_JPEG_TIFF_Tags; + if ( haveIPTC ) { + + bool haveDigest = false; + PSIR_Manager::ImgRsrcInfo digestInfo; + if ( havePSIR ) haveDigest = psir.GetImgRsrc ( kPSIR_IPTCDigest, &digestInfo ); + if ( digestInfo.dataLen != 16 ) haveDigest = false; + + if ( ! haveDigest ) { + iptcDigestState = kDigestMissing; + } else { + iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, iptcInfo.dataLen, digestInfo.dataPtr ); + } + } - + XMP_OptionBits options = 0; if ( this->containsXMP ) options |= k2XMP_FileHadXMP; if ( haveExif ) options |= k2XMP_FileHadExif; if ( haveIPTC ) options |= k2XMP_FileHadIPTC; - + // Process the main XMP packet. If it fails to parse, do a forced legacy import but still throw // an exception. This tells the caller that an error happened, but gives them recovered legacy // should they want to proceed with that. + bool haveXMP = false; + if ( ! this->xmpPacket.empty() ) { XMP_Assert ( this->containsXMP ); // Common code takes care of packetInfo.charForm, .padSize, and .writeable. @@ -651,9 +642,12 @@ 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 ); - ImportJTPtoXMP ( kXMP_JPEGFile, lastLegacy, &exif, psir, &iptc, &this->xmpObj, options ); + 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. } } @@ -661,7 +655,7 @@ void JPEG_MetaHandler::ProcessXMP() // Process the extended XMP if it has a matching GUID. if ( ! this->extendedXMP.empty() ) { - + bool found; GUID_32 g32; std::string extGUID, extPacket; @@ -678,7 +672,7 @@ void JPEG_MetaHandler::ProcessXMP() ((guidPos != this->extendedXMP.end()) ? "Found" : "Missing"), extGUID.c_str() ); #endif } - + if ( guidPos != this->extendedXMP.end() ) { try { XMP_StringPtr extStr = guidPos->second.c_str(); @@ -691,12 +685,16 @@ void JPEG_MetaHandler::ProcessXMP() } } - + // Process the legacy metadata. - ImportJTPtoXMP ( kXMP_JPEGFile, lastLegacy, &exif, psir, &iptc, &this->xmpObj, options ); - if ( haveExif | haveIPTC ) this->containsXMP = true; // Assume we had something for the XMP. - + if ( haveIPTC && (! haveXMP) && (iptcDigestState == kDigestMatches) ) iptcDigestState = kDigestMissing; + bool parseIPTC = (iptcDigestState != kDigestMatches) || (! readOnly); + if ( parseIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); + ImportPhotoData ( exif, iptc, psir, iptcDigestState, &this->xmpObj, options ); + + this->containsXMP = true; // Assume we had something for the XMP. + } // JPEG_MetaHandler::ProcessXMP // ================================================================================================= @@ -706,25 +704,38 @@ void JPEG_MetaHandler::ProcessXMP() void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) { XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. - - // Decide whether to do an in-place update. This can only happen if all of the following are true: - // - There is a standard packet in the file. - // - There is no extended XMP in the file. - // - The are no changes to the legacy Exif or PSIR portions. (The IPTC is in the PSIR.) - // - The new XMP can fit in the old space, without extensions. - ExportXMPtoJTP ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->psirMgr, this->iptcMgr ); - XMP_Int64 oldPacketOffset = this->packetInfo.offset; XMP_Int32 oldPacketLength = this->packetInfo.length; - + if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; + + bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0)); + + // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and + // exif: copies from the XMP, so reserialize the now final XMP packet. - bool doInPlace = (this->xmpPacket.size() <= (size_t)this->packetInfo.length); + ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, this->psirMgr ); + try { + XMP_OptionBits options = kXMP_UseCompactFormat; + if ( fileHadXMP ) options |= kXMP_ExactPacketLength; + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength ); + } catch ( ... ) { + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + // Decide whether to do an in-place update. This can only happen if all of the following are true: + // - There is a standard packet in the file. + // - There is no extended XMP in the file. + // - The are no changes to the legacy Exif or PSIR portions. (The IPTC is in the PSIR.) + // - The new XMP can fit in the old space, without extensions. + + bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength)); + if ( ! this->extendedXMP.empty() ) doInPlace = false; - + if ( (this->exifMgr != 0) && (this->exifMgr->IsLegacyChanged()) ) doInPlace = false; if ( (this->psirMgr != 0) && (this->psirMgr->IsLegacyChanged()) ) doInPlace = false; @@ -733,39 +744,39 @@ void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) #if GatherPerformanceData sAPIPerf->back().extraInfo += ", JPEG in-place update"; #endif - + if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { // They ought to match, cheap to be sure. size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); this->xmpPacket.append ( extraSpace, ' ' ); } - + LFA_FileRef liveFile = this->parent->fileRef; std::string & newPacket = this->xmpPacket; - + XMP_Assert ( newPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic. - + LFA_Seek ( liveFile, oldPacketOffset, SEEK_SET ); LFA_Write ( liveFile, newPacket.c_str(), (XMP_Int32)newPacket.size() ); - + } else { #if GatherPerformanceData sAPIPerf->back().extraInfo += ", JPEG copy update"; #endif - + std::string origPath = this->parent->filePath; LFA_FileRef origRef = this->parent->fileRef; - + std::string updatePath; LFA_FileRef updateRef = 0; - + CreateTempFile ( origPath, &updatePath, kCopyMacRsrc ); updateRef = LFA_Open ( updatePath.c_str(), 'w' ); - + this->parent->filePath = updatePath; this->parent->fileRef = updateRef; - + try { XMP_Assert ( ! this->skipReconcile ); this->skipReconcile = true; @@ -774,21 +785,22 @@ void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) } catch ( ... ) { this->skipReconcile = false; LFA_Close ( updateRef ); + LFA_Delete ( updatePath.c_str() ); this->parent->filePath = origPath; this->parent->fileRef = origRef; throw; } - + LFA_Close ( origRef ); LFA_Delete ( origPath.c_str() ); - + LFA_Close ( updateRef ); LFA_Rename ( updatePath.c_str(), origPath.c_str() ); this->parent->filePath = origPath; this->parent->fileRef = 0; } - + this->needsUpdate = false; } // JPEG_MetaHandler::UpdateFile @@ -809,7 +821,7 @@ void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) { LFA_FileRef destRef = this->parent->fileRef; - + XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); @@ -818,7 +830,7 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so size_t segLen; // ! Must be a size to hold at least 64k+2. IOBuffer ioBuf; XMP_Uns32 first4; - + XMP_Assert ( kIOBufferSize >= (2 + 64*1024) ); // Enough for a marker plus maximum contents. if ( LFA_Measure ( sourceRef ) == 0 ) return; // Tolerate empty files. @@ -826,27 +838,29 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so LFA_Truncate (destRef, 0 ); if ( ! skipReconcile ) { - ExportXMPtoJTP ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->psirMgr, this->iptcMgr ); + // Update the IPTC-IIM and native TIFF/Exif metadata, and reserialize the now final XMP packet. + ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, this->psirMgr ); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); } - + RefillBuffer ( sourceRef, &ioBuf ); if ( ! CheckFileSpace ( sourceRef, &ioBuf, 4 ) ) { XMP_Throw ( "JPEG must have at least SOI and EOI markers", kXMPErr_BadJPEG ); } - + marker = GetUns16BE ( ioBuf.ptr ); if ( marker != 0xFFD8 ) XMP_Throw ( "Missing SOI marker", kXMPErr_BadJPEG ); LFA_Write ( destRef, ioBuf.ptr, 2 ); ioBuf.ptr += 2; - + // Copy the leading APP0 marker segments. - + while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "JPEG_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); } - + if ( ! CheckFileSpace ( sourceRef, &ioBuf, 2 ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG ); marker = GetUns16BE ( ioBuf.ptr ); if ( marker == 0xFFFF ) { @@ -856,49 +870,49 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so } if ( marker != 0xFFE0 ) break; - + if ( ! CheckFileSpace ( sourceRef, &ioBuf, 4 ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG ); segLen = GetUns16BE ( ioBuf.ptr+2 ); segLen += 2; // ! Don't do above in case machine does 16 bit "+". - + if ( ! CheckFileSpace ( sourceRef, &ioBuf, segLen ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG ); LFA_Write ( destRef, ioBuf.ptr, (XMP_Int32)segLen ); ioBuf.ptr += segLen; - + } // Write the new Exif APP1 marker segment. - + if ( this->exifMgr != 0 ) { void* exifPtr; XMP_Uns32 exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr ); - if ( exifLen > kExifMaxDataLength ) exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr, true ); + if ( exifLen > kExifMaxDataLength ) exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr, true /* compact */ ); if ( exifLen > kExifMaxDataLength ) XMP_Throw ( "Overflow of Exif APP1 data", kXMPErr_BadJPEG ); - + if ( exifLen > 0 ) { first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExifSignatureLength + exifLen ); LFA_Write ( destRef, &first4, 4 ); LFA_Write ( destRef, kExifSignatureString, kExifSignatureLength ); LFA_Write ( destRef, exifPtr, exifLen ); } - + } // Write the new XMP APP1 marker segment, with possible extension marker segments. - + std::string mainXMP, extXMP, extDigest; SXMPUtils::PackageForJPEG ( this->xmpObj, &mainXMP, &extXMP, &extDigest ); XMP_Assert ( (extXMP.size() == 0) || (extDigest.size() == 32) ); - + first4 = MakeUns32BE ( 0xFFE10000 + 2 + kMainXMPSignatureLength + (XMP_Uns32)mainXMP.size() ); LFA_Write ( destRef, &first4, 4 ); LFA_Write ( destRef, kMainXMPSignatureString, kMainXMPSignatureLength ); LFA_Write ( destRef, mainXMP.c_str(), (XMP_Int32)mainXMP.size() ); - + size_t extPos = 0; size_t extLen = extXMP.size(); - + while ( extLen > 0 ) { size_t partLen = extLen; @@ -906,7 +920,7 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExtXMPPrefixLength + (XMP_Uns32)partLen ); LFA_Write ( destRef, &first4, 4 ); - + LFA_Write ( destRef, kExtXMPSignatureString, kExtXMPSignatureLength ); LFA_Write ( destRef, extDigest.c_str(), (XMP_Int32)extDigest.size() ); @@ -914,39 +928,39 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so LFA_Write ( destRef, &first4, 4 ); first4 = MakeUns32BE ( (XMP_Int32)extPos ); LFA_Write ( destRef, &first4, 4 ); - + LFA_Write ( destRef, &extXMP[extPos], (XMP_Int32)partLen ); - + extPos += partLen; extLen -= partLen; - + } // Write the new PSIR APP13 marker segment. - + if ( this->psirMgr != 0 ) { void* psirPtr; XMP_Uns32 psirLen = this->psirMgr->UpdateMemoryResources ( &psirPtr ); if ( psirLen > kPSIRMaxDataLength ) XMP_Throw ( "Overflow of PSIR APP13 data", kXMPErr_BadJPEG ); - + if ( psirLen > 0 ) { first4 = MakeUns32BE ( 0xFFED0000 + 2 + kPSIRSignatureLength + psirLen ); LFA_Write ( destRef, &first4, 4 ); LFA_Write ( destRef, kPSIRSignatureString, kPSIRSignatureLength ); LFA_Write ( destRef, psirPtr, psirLen ); } - + } - - // Copy remaining marker segments, skipping old metadata, to the first SOFn marker. - + + // Copy remaining marker segments, skipping old metadata, to the first SOS marker or to EOI. + while ( true ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "JPEG_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); } - + if ( ! CheckFileSpace ( sourceRef, &ioBuf, 2 ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG ); marker = GetUns16BE ( ioBuf.ptr ); if ( marker == 0xFFFF ) { @@ -954,17 +968,22 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so ++ioBuf.ptr; continue; } + + if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) break; // Quit at the first SOS marker or at EOI. - if ( ! TableOrDataMarker ( 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 ( sourceRef, &ioBuf, 4 ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG ); segLen = GetUns16BE ( ioBuf.ptr+2 ); - + if ( ! CheckFileSpace ( sourceRef, &ioBuf, 2+segLen ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG ); bool copySegment = true; XMP_Uns8* signaturePtr = ioBuf.ptr + 4; - + if ( marker == 0xFFED ) { if ( (segLen >= kPSIRSignatureLength) && CheckBytes ( signaturePtr, kPSIRSignatureString, kPSIRSignatureLength ) ) { @@ -987,22 +1006,22 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so if ( copySegment ) LFA_Write ( destRef, 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]); LFA_Write ( destRef, ioBuf.ptr, (XMP_Int32)bufTail ); ioBuf.ptr += bufTail; - + while ( true ) { RefillBuffer ( sourceRef, &ioBuf ); if ( ioBuf.len == 0 ) break; LFA_Write ( destRef, ioBuf.ptr, (XMP_Int32)ioBuf.len ); ioBuf.ptr += ioBuf.len; } - + this->needsUpdate = false; } // JPEG_MetaHandler::WriteFile diff --git a/source/XMPFiles/FileHandlers/JPEG_Handler.hpp b/source/XMPFiles/FileHandlers/JPEG_Handler.hpp index 8a5c0f1..650a5b5 100644 --- a/source/XMPFiles/FileHandlers/JPEG_Handler.hpp +++ b/source/XMPFiles/FileHandlers/JPEG_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -38,7 +38,6 @@ static const XMP_OptionBits kJPEG_HandlerFlags = (kXMPFiles_CanInjectXMP | kXMPFiles_CanReconcile | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | - kXMPFiles_ReturnsTNail | kXMPFiles_AllowsSafeUpdate); class JPEG_MetaHandler : public XMPFileHandler @@ -46,7 +45,6 @@ class JPEG_MetaHandler : public XMPFileHandler public: void CacheFileData(); - void ProcessTNail(); void ProcessXMP(); void UpdateFile ( bool doSafeUpdate ); diff --git a/source/XMPFiles/FileHandlers/MOV_Handler.cpp b/source/XMPFiles/FileHandlers/MOV_Handler.cpp deleted file mode 100644 index e2ea26c..0000000 --- a/source/XMPFiles/FileHandlers/MOV_Handler.cpp +++ /dev/null @@ -1,996 +0,0 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -#include "XMP_Environment.h" // ! This must be the first include. -#if ! ( XMP_64 || XMP_UNIXBuild) // Closes at very bottom. - -#if XMP_WinBuild - #pragma warning ( disable : 4996 ) // '...' was declared deprecated -#endif - -#include "MOV_Handler.hpp" -#include "QuickTime_Support.hpp" - -#include "QuickTimeComponents.h" - -#if XMP_WinBuild - #include "QTML.h" - #include "Movies.h" -#endif - -#if XMP_MacBuild - #include <Movies.h> -#endif - -using namespace std; - -static OSType kXMPUserDataType = 'XMP_'; -static long kXMPUserDataTypeIndex = 1; - -static bool CreatorAtom_SetProperties ( SXMPMeta& xmpObj, - const MOV_MetaHandler::CreatorAtomStrings& creatorAtomStrings ); - -static bool CreatorAtom_Update ( SXMPMeta& xmpObj, UserData& movieUserData ); - -static bool CreatorAtom_ReadStrings ( MOV_MetaHandler::CreatorAtomStrings& creatorAtomStrings, - UserData& movieUserData ); - -// ================================================================================================= -/// \file MOV_Handler.cpp -/// \brief File format handler for MOV. -/// -/// This header ... -/// -// ================================================================================================= - -// ================================================================================================= -// MOV_MetaHandlerCTor -// =================== - -XMPFileHandler * MOV_MetaHandlerCTor ( XMPFiles * parent ) -{ - return new MOV_MetaHandler ( parent ); - -} // MOV_MetaHandlerCTor - -// ================================================================================================= -// MOV_CheckFormat -// =============== - -bool MOV_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef fileRef, - XMPFiles * parent ) -{ - IgnoreParam(format); IgnoreParam(fileRef); - - XMP_Assert ( format == kXMP_MOVFile ); - XMP_Assert ( fileRef == 0 ); - - bool inBackground = XMP_OptionIsSet ( parent->openFlags, kXMPFiles_OpenInBackground ); - - if ( parent->format != kXMP_MOVFile ) return false; // Check the first call hint, QT is promiscuous. - if ( ! QuickTime_Support::sMainInitOK ) return false; - - if ( inBackground ) { - if ( ! QuickTime_Support::ThreadInitialize() ) return false; - } - - bool isMov = false; - OSErr err = noErr; - - Handle qtDataRef = 0; - OSType qtRefType; - Movie tempMovie = 0; - short flags; - - CFStringRef cfsPath = CFStringCreateWithCString ( 0, filePath, kCFStringEncodingUTF8 ); - if ( cfsPath == 0 ) return false; // ? Throw? - - err = QTNewDataReferenceFromFullPathCFString ( cfsPath, kQTNativeDefaultPathStyle, 0, &qtDataRef, &qtRefType ); - if ( err != noErr ) goto EXIT; - - flags = newMovieDontResolveDataRefs | newMovieDontAskUnresolvedDataRefs; - err = NewMovieFromDataRef ( &tempMovie, flags, 0, qtDataRef, qtRefType ); - if ( err != noErr ) goto EXIT; - - isMov = true; - -EXIT: - - if ( tempMovie != 0 ) DisposeMovie ( tempMovie ); - if ( qtDataRef != 0 ) DisposeHandle ( qtDataRef ); - if ( cfsPath != 0 ) CFRelease ( cfsPath ); - - if ( inBackground && (! isMov) ) QuickTime_Support::ThreadTerminate(); - return isMov; - -} // MOV_CheckFormat - -// ================================================================================================= -// MOV_MetaHandler::MOV_MetaHandler -// ================================ - -MOV_MetaHandler::MOV_MetaHandler ( XMPFiles * _parent ) - : mQTInit(false), mMovieDataRef(0), mMovieDataHandler(0), mMovie(0), mMovieResourceID(0), mFilePermission(0) -{ - - this->parent = _parent; - this->handlerFlags = kMOV_HandlerFlags; - this->stdCharForm = kXMP_Char8Bit; - -} // MOV_MetaHandler::MOV_MetaHandler - -// ================================================================================================= -// MOV_MetaHandler::~MOV_MetaHandler -// ================================= - -MOV_MetaHandler::~MOV_MetaHandler() -{ - bool inBackground = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenInBackground ); - - this->CloseMovie(); - if ( inBackground ) QuickTime_Support::ThreadTerminate(); - -} // MOV_MetaHandler::~MOV_MetaHandler - -// ================================================================================================= -// MOV_MetaHandler::ProcessXMP -// =========================== - -void MOV_MetaHandler::ProcessXMP() -{ - if ( (!this->containsXMP) || this->processedXMP ) return; - - if ( this->handlerFlags & kXMPFiles_CanReconcile ) { - XMP_Throw ( "Reconciling file handlers must implement ProcessXMP", kXMPErr_InternalFailure ); - } - - SXMPUtils::RemoveProperties ( &this->xmpObj, 0, 0, kXMPUtil_DoAllProperties ); - this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); - this->processedXMP = true; - - CreatorAtom_SetProperties ( this->xmpObj, mCreatorAtomStrings ); - -} - -// ================================================================================================= -// MOV_MetaHandler::UpdateFile -// =========================== - -void MOV_MetaHandler::UpdateFile ( bool doSafeUpdate ) -{ - if ( ! this->needsUpdate ) return; - if ( doSafeUpdate ) XMP_Throw ( "MOV_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); - - XMP_StringPtr packetStr = this->xmpPacket.c_str(); - XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); - - if ( packetLen == 0 ) return; // Bail if no XMP packet - - if ( this->OpenMovie ( (kDataHCanRead | kDataHCanWrite) ) ) { - - UserData movieUserData ( GetMovieUserData ( this->mMovie ) ); - if ( movieUserData != 0 ) { - - OSErr err; - - // Remove previous versions - err = GetUserData ( movieUserData, 0, kXMPUserDataType, kXMPUserDataTypeIndex ); - if ( err == noErr ) { - RemoveUserData(movieUserData, kXMPUserDataType, kXMPUserDataTypeIndex); - } - - // Add the new one - Handle XMPdata ( NewHandle(packetLen) ); - if ( XMPdata != 0 ) { - HLock ( XMPdata ); - strncpy ( *XMPdata, packetStr, packetLen ); // AUDIT: Handle created above using packetLen. - HUnlock ( XMPdata ); - err = AddUserData ( movieUserData, XMPdata, kXMPUserDataType ); - DisposeHandle ( XMPdata ); - } - - CreatorAtom_Update ( this->xmpObj, movieUserData ); - - } - - } - - this->needsUpdate = false; - -} // MOV_MetaHandler::UpdateFile - -// ================================================================================================= -// MOV_MetaHandler::WriteFile -// ========================== - -void MOV_MetaHandler::WriteFile ( LFA_FileRef sourceRef, - const std::string & sourcePath ) -{ - IgnoreParam(sourceRef); IgnoreParam(sourcePath); - - XMP_Throw ( "MOV_MetaHandler::WriteFile: Not supported", kXMPErr_Unavailable ); - -} // MOV_MetaHandler::WriteFile - -// ================================================================================================= -// MOV_MetaHandler::OpenMovie -// ========================== - -bool MOV_MetaHandler::OpenMovie ( long inPermission ) -{ - // If the file is already opened with the correct permission, bail - if ( (inPermission == this->mFilePermission) && (this->mMovie != 0) ) return true; - - // If already open, close to open with new permissions - if ( (this->mMovieDataRef != 0) || (this->mMovieDataHandler != 0) || (this->mMovie != 0) ) this->CloseMovie(); - XMP_Assert ( (this->mMovieDataRef == 0) && (this->mMovieDataHandler == 0) && (this->mMovie == 0) ); - - OSErr err = noErr; - OSType qtRefType; - short flags; - - CFStringRef cfsPath = CFStringCreateWithCString ( 0, this->parent->filePath.c_str(), kCFStringEncodingUTF8 ); - if ( cfsPath == 0 ) return false; // ? Throw? - - err = QTNewDataReferenceFromFullPathCFString ( cfsPath, kQTNativeDefaultPathStyle, 0, - &this->mMovieDataRef, &qtRefType ); - if ( err != noErr ) goto FAILURE; - - this->mFilePermission = inPermission; - err = OpenMovieStorage ( this->mMovieDataRef, qtRefType, this->mFilePermission, &this->mMovieDataHandler ); - if ( err != noErr ) goto FAILURE; - - flags = newMovieDontAskUnresolvedDataRefs; - this->mMovieResourceID = 0; // *** Is this the right input value? - err = NewMovieFromDataRef ( &this->mMovie, flags, &this->mMovieResourceID, this->mMovieDataRef, qtRefType ); - if ( err != noErr ) goto FAILURE; - - if ( cfsPath != 0 ) CFRelease ( cfsPath ); - return true; - -FAILURE: - - if ( this->mMovie != 0 ) DisposeMovie ( this->mMovie ); - if ( this->mMovieDataHandler != 0 ) CloseMovieStorage ( this->mMovieDataHandler ); - if ( this->mMovieDataRef != 0 ) DisposeHandle ( this->mMovieDataRef ); - - this->mMovie = 0; - this->mMovieDataHandler = 0; - this->mMovieDataRef = 0; - - if ( cfsPath != 0 ) CFRelease ( cfsPath ); - return false; - -} // MOV_MetaHandler::OpenMovie - -// ================================================================================================= -// MOV_MetaHandler::CloseMovie -// =========================== - -void MOV_MetaHandler::CloseMovie() -{ - - if ( this->mMovie != 0 ) { - if ( HasMovieChanged ( this->mMovie ) ) { // If the movie has been modified, write it to disk. - (void) UpdateMovieInStorage ( this->mMovie, this->mMovieDataHandler ); - } - DisposeMovie ( this->mMovie ); - } - - if ( this->mMovieDataHandler != 0 ) CloseMovieStorage ( this->mMovieDataHandler ); - if ( this->mMovieDataRef != 0 ) DisposeHandle ( this->mMovieDataRef ); - - this->mMovie = 0; - this->mMovieDataHandler = 0; - this->mMovieDataRef = 0; - -} // MOV_MetaHandler::CloseMovie - -// ================================================================================================= -// GetAtomInfo -// =========== - -struct AtomInfo { - XMP_Int64 atomSize; - XMP_Uns32 atomType; - bool hasLargeSize; -}; - -enum { // ! Do not rearrange, code depends on this order. - kBadQT_NoError = 0, // No errors. - kBadQT_SmallInner = 1, // An extra 1..7 bytes at the end of an inner span. - kBadQT_LargeInner = 2, // More serious inner garbage, found as invalid atom length. - kBadQT_SmallOuter = 3, // An extra 1..7 bytes at the end of the file. - kBadQT_LargeOuter = 4 // More serious EOF garbage, found as invalid atom length. -}; -typedef XMP_Uns8 QTErrorMode; - -static QTErrorMode GetAtomInfo ( const LFA_FileRef qtFile, XMP_Int64 spanSize, int nesting, AtomInfo * info ) -{ - QTErrorMode status = kBadQT_NoError; - XMP_Uns8 buffer [8]; - - info->hasLargeSize = false; - - LFA_Read ( qtFile, buffer, 8, kLFA_RequireAll ); // Will throw if 8 bytes aren't available. - info->atomSize = GetUns32BE ( &buffer[0] ); // ! Yes, the initial size is big endian UInt32. - info->atomType = GetUns32BE ( &buffer[4] ); - - if ( info->atomSize == 0 ) { // Does the atom extend to EOF? - - if ( nesting != 0 ) return kBadQT_LargeInner; - info->atomSize = spanSize; // This outer atom goes to EOF. - - } else if ( info->atomSize == 1 ) { // Does the atom have a 64-bit size? - - if ( spanSize < 16 ) { // Is there room in the span for the 16 byte header? - status = kBadQT_LargeInner; - if ( nesting == 0 ) status += 2; // Convert to "outer". - return status; - } - - LFA_Read ( qtFile, buffer, 8, kLFA_RequireAll ); - info->atomSize = (XMP_Int64) GetUns64BE ( &buffer[0] ); - info->hasLargeSize = true; - - } - - XMP_Assert ( status == kBadQT_NoError ); - return status; - -} // GetAtomInfo - -// ================================================================================================= -// CheckAtomList -// ============= -// -// Check that a sequence of atoms fills a given span. The I/O position must be at the start of the -// span, it is left just past the span on success. Recursive checks are done for top level 'moov' -// atoms, and second level 'udta' atoms ('udta' inside 'moov'). -// -// Checking continues for "small inner" errors. They will be reported if no other kinds of errors -// are found, otherwise the other error is reported. Checking is immediately aborted for any "large" -// error. The rationale is that QuickTime can apparently handle small inner errors. They might be -// arise from updates that shorten an atom by less than 8 bytes. Larger shrinkage should introduce a -// 'free' atom. - -static QTErrorMode CheckAtomList ( const LFA_FileRef qtFile, XMP_Int64 spanSize, int nesting ) -{ - QTErrorMode status = kBadQT_NoError; - AtomInfo info; - - const static XMP_Uns32 moovAtomType = 0x6D6F6F76; // ! Don't use MakeUns32BE, already big endian. - const static XMP_Uns32 udtaAtomType = 0x75647461; - - for ( ; spanSize >= 8; spanSize -= info.atomSize ) { - - QTErrorMode atomStatus = GetAtomInfo ( qtFile, spanSize, nesting, &info ); - if ( atomStatus != kBadQT_NoError ) return atomStatus; - - XMP_Int64 headerSize = 8; - if ( info.hasLargeSize ) headerSize = 16; - - if ( (info.atomSize < headerSize) || (info.atomSize > spanSize) ) { - status = kBadQT_LargeInner; - if ( nesting == 0 ) status += 2; // Convert to "outer". - return status; - } - - bool doChildren = false; - if ( (nesting == 0) && (info.atomType == moovAtomType) ) doChildren = true; - if ( (nesting == 1) && (info.atomType == udtaAtomType) ) doChildren = true; - - XMP_Int64 dataSize = info.atomSize - headerSize; - - if ( ! doChildren ) { - LFA_Seek ( qtFile, dataSize, SEEK_CUR ); - } else { - QTErrorMode innerStatus = CheckAtomList ( qtFile, dataSize, nesting+1 ); - if ( innerStatus > kBadQT_SmallInner ) return innerStatus; // Quit for serious errors. - if ( status == kBadQT_NoError ) status = innerStatus; // Remember small inner errors. - } - - } - - XMP_Assert ( status <= kBadQT_SmallInner ); // Else already returned. - // ! Make sure inner kBadQT_SmallInner is propagated if this span is OK. - - if ( spanSize != 0 ) { - LFA_Seek ( qtFile, spanSize, SEEK_CUR ); // ! Skip the trailing garbage of this span. - status = kBadQT_SmallInner; - if ( spanSize >= 8 ) status = kBadQT_LargeInner; - if ( nesting == 0 ) status += 2; // Convert to "outer". - } - - return status; - -} // CheckAtomList - -// ================================================================================================= -// AttemptFileRepair -// ================= - -static void AttemptFileRepair ( LFA_FileRef qtFile, XMP_Int64 fileSpace, QTErrorMode status ) -{ - - switch ( status ) { - case kBadQT_NoError : return; // Sanity check. - case kBadQT_SmallInner : return; // Ignore these, QT seems to be able to handle them. - case kBadQT_LargeInner : XMP_Throw ( "Can't repair QuickTime file", kXMPErr_BadFileFormat ); - case kBadQT_SmallOuter : break; // Truncate file below. - case kBadQT_LargeOuter : break; // Truncate file below. - default : XMP_Throw ( "Invalid QuickTime error mode", kXMPErr_InternalFailure ); - } - - AtomInfo info; - XMP_Int64 headerSize; - - // Process the top level atoms until an error is found. - - LFA_Seek ( qtFile, 0, SEEK_SET ); - - for ( ; fileSpace >= 8; fileSpace -= info.atomSize ) { - - QTErrorMode atomStatus = GetAtomInfo ( qtFile, fileSpace, 0, &info ); - - headerSize = 8; // ! Set this before checking atomStatus, used after the loop. - if ( info.hasLargeSize ) headerSize = 16; - - if ( atomStatus != kBadQT_NoError ) break; - if ( (info.atomSize < headerSize) || (info.atomSize > fileSpace) ) break; - - XMP_Int64 dataSize = info.atomSize - headerSize; - LFA_Seek ( qtFile, dataSize, SEEK_CUR ); - - } - - // Truncate the file. If fileSpace >= 8 then the loop exited early due to a bad atom, seek back - // to the atom's start. Otherwise, the loop exited because no mmore atoms are possible, no seek. - - if ( fileSpace >= 8 ) LFA_Seek ( qtFile, -headerSize, SEEK_CUR ); - XMP_Int64 currPos = LFA_Tell ( qtFile ); - LFA_Truncate ( qtFile, currPos ); - -} // AttemptFileRepair - -// ================================================================================================= -// CheckFileStructure -// ================== - -static void CheckFileStructure ( XMPFileHandler * thiz, bool doRepair ) -{ - XMPFiles * parent = thiz->parent; - - // Open the disk file so we can look inside and maybe repair. - - AutoFile localFile; // ! Don't use parent->fileRef keep this usage private. - XMP_Assert ( parent->fileRef == 0 ); // The file should not be open yet. - - char openMode = 'r'; - if ( doRepair ) openMode = 'w'; - localFile.fileRef = LFA_Open ( parent->filePath.c_str(), openMode ); - if ( localFile.fileRef == 0 ) XMP_Throw ( "Can't open QuickTime file for update checks", kXMPErr_ExternalFailure ); - XMP_Int64 fileSize = LFA_Measure ( localFile.fileRef ); - - // Check the basic file structure and try to repair if asked. - - QTErrorMode status = CheckAtomList ( localFile.fileRef, fileSize, 0 ); - - if ( status != kBadQT_NoError ) { - if ( doRepair ) { - AttemptFileRepair ( localFile.fileRef, fileSize, status ); // Will throw if the attempt fails. - } else if ( status != kBadQT_SmallInner ) { - XMP_Throw ( "Ill-formed QuickTime file", kXMPErr_BadFileFormat ); - } else { - return; // ! Ignore these, QT seems to be able to handle them. - // *** Might want to throw for check-only, ignore when repairing. - } - } - -} // CheckFileStructure; - - -// ================================================================================================= -// MOV_MetaHandler::CacheFileData -// ============================== - -void MOV_MetaHandler::CacheFileData() -{ - - // Pre-check files opened for update. We've found bugs in Apple's QT code that make slightly - // ill-formed files unreadable. - - XMPFiles * parent = this->parent; - - const bool isUpdate = XMP_OptionIsSet ( parent->openFlags, kXMPFiles_OpenForUpdate ); - const bool doRepair = XMP_OptionIsSet ( parent->openFlags, kXMPFiles_OpenRepairFile ); - - if ( isUpdate ) { - CheckFileStructure ( this, doRepair ); // Will throw for failure. - } - - // Continue with the usual caching of the file's metadata. - - this->containsXMP = false; - - if ( this->OpenMovie ( kDataHCanRead ) ) { - - UserData movieUserData ( GetMovieUserData ( this->mMovie ) ); - if ( movieUserData != 0 ) { - - Handle XMPdataHandle ( NewHandle(0) ); - if ( XMPdataHandle != 0 ) { - - OSErr err = GetUserData ( movieUserData, XMPdataHandle, kXMPUserDataType, kXMPUserDataTypeIndex ); - if (err != noErr) { // userDataItemNotFound = -2026 - - packetInfo.writeable = true; // If no packet found, created packets will be writeable - - } else { - - // Convert handles data, to std::string raw - this->xmpPacket.clear(); - size_t dataSize = GetHandleSize ( XMPdataHandle ); - HLock(XMPdataHandle); - this->xmpPacket.assign ( (const char*)(*XMPdataHandle), dataSize ); - HUnlock ( XMPdataHandle ); - - this->packetInfo.offset = kXMPFiles_UnknownOffset; - this->packetInfo.length = (XMP_Int32)dataSize; - this->containsXMP = true; - - CreatorAtom_ReadStrings ( mCreatorAtomStrings, movieUserData ); - - } - - DisposeHandle ( XMPdataHandle ); - - } - - } - - } - -} // MOV_MetaHandler::CacheFileData - -// ================================================================================================= -// ================================================================================================= - -// *** Could be pulled out, maybe refactored and partly shared with AVI and WAV. - -#pragma pack(push,1) - -// [TODO] Can we switch to using just a full path here? -struct FSSpecLegacy -{ - short vRefNum; - long parID; - char name[260]; // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp -- 260 is "old school", 32000 is "new school". -}; - -struct CR8R_CreatorAtom -{ - unsigned long magicLu; - - long atom_sizeL; // Size of this structure. - short atom_vers_majorS; // Major atom version. - short atom_vers_minorS; // Minor atom version. - - // mac - unsigned long creator_codeLu; // Application code on MacOS. - unsigned long creator_eventLu; // Invocation appleEvent. - - // windows - char creator_extAC[16]; // Extension allowing registry search to app. - char creator_flagAC[16]; // Flag passed to app at invocation time. - - char creator_nameAC[32]; // Name of the creator application. -}; - -typedef CR8R_CreatorAtom** CR8R_CreatorAtomHandle; - -typedef enum -{ - Embed_ExportTypeMovie = 0, - Embed_ExportTypeStill, - Embed_ExportTypeAudio, - Embed_ExportTypeCustom -} -Embed_ExportType; - - -struct Embed_ProjectLinkAtom -{ - // header data - unsigned long magicLu; - long atom_sizeL; - short atom_vers_apiS; - short atom_vers_codeS; - - // the link data - unsigned long exportType; // See enum. The type of export that generated the file - - // [TODO] Can we switch to using just a full path here? - FSSpecLegacy fullPath; // Full path of the project file -}; - -#pragma pack(pop) - -// ------------------------------------------------------------------------------------------------- - -#define kCreatorTool "CreatorTool" -#define AdobeCreatorAtomVersion_Major 1 -#define AdobeCreatorAtomVersion_Minor 0 -#define AdobeCreatorAtom_Magic 0xBEEFCAFE - -#define myCreatorAtom MakeFourCC ( 'C','r','8','r' ) - -static void CreatorAtom_Initialize ( CR8R_CreatorAtom& creatorAtom ) -{ - memset ( &creatorAtom, 0, sizeof(CR8R_CreatorAtom) ); - creatorAtom.magicLu = AdobeCreatorAtom_Magic; - creatorAtom.atom_vers_majorS = AdobeCreatorAtomVersion_Major; - creatorAtom.atom_vers_minorS = AdobeCreatorAtomVersion_Minor; - creatorAtom.atom_sizeL = sizeof(CR8R_CreatorAtom); -} - -// ------------------------------------------------------------------------------------------------- - -#define PR_PROJECT_LINK_ATOM_VERS_API 1 -#define PR_PROJECT_LINK_ATOM_VERS_CODE 0 -#define PR_PROJECT_LINK_ATOM_TYPE 'PrmL' -#define PR_PROJECT_LINK_MAGIC 0x600DF00D // GoodFood - -#define myProjectLink MakeFourCC ( 'P','r','m','L') - -// ------------------------------------------------------------------------------------------------- - -static void CreatorAtom_MakeValid ( CR8R_CreatorAtom * creator_atomP ) -{ - // If already valid, no conversion is needed. - if ( creator_atomP->magicLu == AdobeCreatorAtom_Magic ) return; - - Flip4 ( &creator_atomP->magicLu ); - Flip2 ( &creator_atomP->atom_vers_majorS ); - Flip2 ( &creator_atomP->atom_vers_minorS ); - - Flip4 ( &creator_atomP->atom_sizeL ); - Flip4 ( &creator_atomP->creator_codeLu ); - Flip4 ( &creator_atomP->creator_eventLu ); - - XMP_Assert ( creator_atomP->magicLu == AdobeCreatorAtom_Magic ); -} - -// ------------------------------------------------------------------------------------------------- - -static void CreatorAtom_ToBE ( CR8R_CreatorAtom * creator_atomP ) -{ - creator_atomP->atom_vers_majorS = MakeUns16BE ( creator_atomP->atom_vers_majorS ); - creator_atomP->atom_vers_minorS = MakeUns16BE ( creator_atomP->atom_vers_minorS ); - - creator_atomP->magicLu = MakeUns32BE ( creator_atomP->magicLu ); - creator_atomP->atom_sizeL = MakeUns32BE ( creator_atomP->atom_sizeL ); - creator_atomP->creator_codeLu = MakeUns32BE ( creator_atomP->creator_codeLu ); - creator_atomP->creator_eventLu = MakeUns32BE ( creator_atomP->creator_eventLu ); -} - -// ------------------------------------------------------------------------------------------------- - -static void ProjectLinkAtom_MakeValid ( Embed_ProjectLinkAtom * link_atomP ) -{ - // If already valid, no conversion is needed. - if ( link_atomP->magicLu == PR_PROJECT_LINK_MAGIC ) return; - - // do the header - Flip4 ( &link_atomP->magicLu ); - Flip4 ( &link_atomP->atom_sizeL ); - Flip2 ( &link_atomP->atom_vers_apiS ); - Flip2 ( &link_atomP->atom_vers_codeS ); - - // do the FSSpec data - Flip2 ( &link_atomP->fullPath.vRefNum ); - Flip4 ( &link_atomP->fullPath.parID ); - - XMP_Assert ( link_atomP->magicLu == PR_PROJECT_LINK_MAGIC ); -} - -// ------------------------------------------------------------------------------------------------- - -static void ProjectLinkAtom_Initialize ( Embed_ProjectLinkAtom& epla, Embed_ExportType type, - const std::string& projectPathString) -{ - - memset ( &epla, 0, sizeof(Embed_ProjectLinkAtom) ); - - epla.magicLu = PR_PROJECT_LINK_MAGIC; - epla.atom_sizeL = epla.atom_vers_apiS = PR_PROJECT_LINK_ATOM_VERS_API; - epla.atom_vers_codeS = PR_PROJECT_LINK_ATOM_VERS_CODE; - epla.atom_sizeL = sizeof(Embed_ProjectLinkAtom); - epla.exportType = type; - epla.fullPath.vRefNum = 0; - epla.fullPath.parID = 0; - - strncpy ( epla.fullPath.name, projectPathString.c_str(), - min ( projectPathString.length(), sizeof(epla.fullPath.name) ) ); - -} - -// ------------------------------------------------------------------------------------------------- - -static bool Mov_ReadProjectLinkAtom ( UserData& movieUserData, Embed_ProjectLinkAtom* epla ) -{ - Handle PrmLdataHandle ( NewHandle(0) ); - if ( PrmLdataHandle == 0 ) return false; - - OSErr err = GetUserData ( movieUserData, PrmLdataHandle, 'PrmL', 1 ); - if ( err != noErr ) return false; - - // Convert handles data, to std::string raw - // std::string PrmLPacket; - // PrmLPacket.clear(); - size_t dataSize = GetHandleSize ( PrmLdataHandle ); - HLock ( PrmLdataHandle ); - - bool ok = (dataSize == sizeof(Embed_ProjectLinkAtom)); - if ( ok ) { - memcpy ( epla, (*PrmLdataHandle), dataSize ); - ProjectLinkAtom_MakeValid ( epla ); - } - - HUnlock ( PrmLdataHandle ); - DisposeHandle ( PrmLdataHandle ); - - return ok; - -} - -// ------------------------------------------------------------------------------------------------- - -static bool Mov_ReadCreatorAtom ( UserData& movieUserData, CR8R_CreatorAtom* creatorAtom ) -{ - Handle PrmLdataHandle ( NewHandle(0) ); - if ( PrmLdataHandle == 0 ) return false; - - OSErr err = GetUserData ( movieUserData, PrmLdataHandle, 'Cr8r', 1 ); - if ( err != noErr ) return false; - - // Convert handles data, to std::string raw - // std::string PrmLPacket; - // PrmLPacket.clear(); - size_t dataSize = GetHandleSize ( PrmLdataHandle ); - HLock ( PrmLdataHandle ); - - bool ok = (sizeof(CR8R_CreatorAtom) == dataSize); - if ( ok ) { - memcpy ( creatorAtom, (*PrmLdataHandle), dataSize ); - CreatorAtom_MakeValid ( creatorAtom ); - } - - HUnlock ( PrmLdataHandle ); - DisposeHandle ( PrmLdataHandle ); - - return ok; - -} - -// ------------------------------------------------------------------------------------------------- - -static bool Mov_WriteCreatorAtom ( UserData& movieUserData, CR8R_CreatorAtom& creatorAtom, bool mustExist ) -{ - Handle PrmLdataHandle ( NewHandle(sizeof(CR8R_CreatorAtom)) ); - if ( PrmLdataHandle == 0 ) return false; - - OSErr err = GetUserData ( movieUserData, PrmLdataHandle, 'Cr8r', 1); - if ( err == noErr ) { - while ( ! RemoveUserData ( movieUserData, 'Cr8r', 1 ) ) {}; - } else { - if ( mustExist ) return false; - } - - // Convert handles data, to std::string raw - // std::string PrmLPacket; - // PrmLPacket.clear(); - size_t dataSize = GetHandleSize ( PrmLdataHandle ); - HLock ( PrmLdataHandle ); - memcpy ( (*PrmLdataHandle), &creatorAtom, sizeof(CR8R_CreatorAtom) ); - CreatorAtom_ToBE ( (CR8R_CreatorAtom*)(*PrmLdataHandle) ); - HUnlock ( PrmLdataHandle ); - - OSErr movieErr = AddUserData ( movieUserData, PrmLdataHandle, 'Cr8r'); - - DisposeHandle ( PrmLdataHandle ); - - return movieErr==0; -} - -// ------------------------------------------------------------------------------------------------- - -#define DoAssignBufferedString(str,buffer) AssignBufferedString ( str, buffer, sizeof(buffer) ) - -static inline void AssignBufferedString ( std::string & str, const char * buffer, size_t maxLen ) -{ - size_t len; - for ( len = 0; (len < maxLen) && (buffer[len] != 0); ++len ) { /* empty body */ } - str.assign ( buffer, len ); -} - -// ------------------------------------------------------------------------------------------------- - -static bool CreatorAtom_ReadStrings ( MOV_MetaHandler::CreatorAtomStrings& creatorAtomStrings, - UserData& movieUserData ) -{ - Embed_ProjectLinkAtom epla; - bool ok = Mov_ReadProjectLinkAtom ( movieUserData, &epla ); - - if ( ok ) { - - std::string projectPathString = epla.fullPath.name; - - if ( ! projectPathString.empty() ) { - - if ( projectPathString[0] == '/' ) { - creatorAtomStrings.posixProjectPath = projectPathString; - } else if ( projectPathString.substr(0,4) == std::string("\\\\?\\") ) { - creatorAtomStrings.uncProjectPath = projectPathString; - } - - switch ( epla.exportType ) { - case Embed_ExportTypeMovie : creatorAtomStrings.projectRefType = "movie"; break; - case Embed_ExportTypeStill : creatorAtomStrings.projectRefType = "still"; break; - case Embed_ExportTypeAudio : creatorAtomStrings.projectRefType = "audio"; break; - case Embed_ExportTypeCustom : creatorAtomStrings.projectRefType = "custom"; break; - } - - } - - } - - CR8R_CreatorAtom creatorAtom; - ok = Mov_ReadCreatorAtom ( movieUserData, &creatorAtom ); - - if ( ok ) { - - char buffer[256]; - - sprintf ( buffer, "%d", creatorAtom.creator_codeLu ); - creatorAtomStrings.applicationCode = buffer; - - sprintf ( buffer, "%d", creatorAtom.creator_eventLu ); - creatorAtomStrings.invocationAppleEvent = buffer; - - DoAssignBufferedString ( creatorAtomStrings.extension, creatorAtom.creator_extAC ); - DoAssignBufferedString ( creatorAtomStrings.invocationFlags, creatorAtom.creator_flagAC ); - DoAssignBufferedString ( creatorAtomStrings.creatorTool, creatorAtom.creator_nameAC ); - - } - - return ok; - -} - -// ------------------------------------------------------------------------------------------------- - -static bool CreatorAtom_SetProperties ( SXMPMeta& xmpObj, - const MOV_MetaHandler::CreatorAtomStrings& creatorAtomStrings ) -{ - if ( ! creatorAtomStrings.posixProjectPath.empty() ) { - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", - kXMP_NS_CreatorAtom, "posixProjectPath", creatorAtomStrings.posixProjectPath, 0 ); - } - - if ( ! creatorAtomStrings.uncProjectPath.empty() ) { - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", - kXMP_NS_CreatorAtom, "uncProjectPath", creatorAtomStrings.uncProjectPath, 0 ); - } - - if ( ! creatorAtomStrings.projectRefType.empty() ) { - xmpObj.SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", creatorAtomStrings.projectRefType.c_str()); - } - - if ( ! creatorAtomStrings.applicationCode.empty() ) { - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", - kXMP_NS_CreatorAtom, "applicationCode", creatorAtomStrings.applicationCode, 0 ); - } - - if ( ! creatorAtomStrings.invocationAppleEvent.empty() ) { - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", - kXMP_NS_CreatorAtom, "invocationAppleEvent", creatorAtomStrings.invocationAppleEvent, 0 ); - } - - if ( ! creatorAtomStrings.extension.empty() ) { - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", - kXMP_NS_CreatorAtom, "extension", creatorAtomStrings.extension, 0 ); - } - - if ( ! creatorAtomStrings.invocationFlags.empty() ) { - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", - kXMP_NS_CreatorAtom, "invocationFlags", creatorAtomStrings.invocationFlags, 0 ); - } - - if ( ! creatorAtomStrings.creatorTool.empty() ) { - xmpObj.SetProperty ( kXMP_NS_XMP, "CreatorTool", creatorAtomStrings.creatorTool, 0 ); - } - - return ok; - -} - -// ------------------------------------------------------------------------------------------------- - -#define EnsureFinalNul(buffer) buffer [ sizeof(buffer) - 1 ] = 0 - -static bool CreatorAtom_Update ( SXMPMeta& xmpObj, - UserData& movieUserData ) -{ - - // Get Creator Atom XMP values. - bool found = false; - std::string posixPathString, uncPathString; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "posixProjectPath", &posixPathString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "uncProjectPath", &uncPathString, 0 ) ) found = true; - - std::string applicationCodeString, invocationAppleEventString, extensionString, invocationFlagsString, creatorToolString; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &applicationCodeString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &invocationAppleEventString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &extensionString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &invocationFlagsString, 0 ) ) found = true; - if ( xmpObj.GetProperty ( kXMP_NS_XMP, "CreatorTool", &creatorToolString, 0 ) ) found = true; - - // If no Creator Atom information found, don't write anything. - if ( ! found ) return false; - - // Read Legacy Creator Atom. - unsigned long creatorAtomSize = 0; - CR8R_CreatorAtom creatorAtomLegacy; - CreatorAtom_Initialize ( creatorAtomLegacy ); - bool ok = Mov_ReadCreatorAtom ( movieUserData, &creatorAtomLegacy ); - - // Generate new Creator Atom from XMP. - CR8R_CreatorAtom creatorAtomViaXMP; - CreatorAtom_Initialize ( creatorAtomViaXMP ); - - if ( ! applicationCodeString.empty() ) { - creatorAtomViaXMP.creator_codeLu = strtoul ( applicationCodeString.c_str(), 0, 0 ); - } - if ( ! invocationAppleEventString.empty() ) { - creatorAtomViaXMP.creator_eventLu = strtoul ( invocationAppleEventString.c_str(), 0, 0 ); - } - if ( ! extensionString.empty() ) { - strncpy ( creatorAtomViaXMP.creator_extAC, extensionString.c_str(), sizeof(creatorAtomViaXMP.creator_extAC) ); - EnsureFinalNul ( creatorAtomViaXMP.creator_extAC ); - } - if ( ! invocationFlagsString.empty() ) { - strncpy ( creatorAtomViaXMP.creator_flagAC, invocationFlagsString.c_str(), sizeof(creatorAtomViaXMP.creator_flagAC) ); - EnsureFinalNul ( creatorAtomViaXMP.creator_flagAC ); - } - if ( ! creatorToolString.empty() ) { - strncpy ( creatorAtomViaXMP.creator_nameAC, creatorToolString.c_str(), sizeof(creatorAtomViaXMP.creator_nameAC) ); - EnsureFinalNul ( creatorAtomViaXMP.creator_nameAC ); - } - - // Write Creator Atom. - if ( ok ) { - // If there's legacy, update if atom generated from XMP doesn't match legacy. - if ( memcmp ( &creatorAtomViaXMP, &creatorAtomLegacy, sizeof(CR8R_CreatorAtom) ) != 0 ) { - ok = Mov_WriteCreatorAtom ( movieUserData, creatorAtomViaXMP, true ); - } - } else { - // Write completely new atom from XMP. - ok = Mov_WriteCreatorAtom ( movieUserData, creatorAtomViaXMP, false ); - } - - return ok; - -} - -// ================================================================================================= - -#endif // XMP_64 || XMP_UNIXBuild diff --git a/source/XMPFiles/FileHandlers/MOV_Handler.hpp b/source/XMPFiles/FileHandlers/MOV_Handler.hpp deleted file mode 100644 index 4d398ce..0000000 --- a/source/XMPFiles/FileHandlers/MOV_Handler.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef __MOV_Handler_hpp__ -#define __MOV_Handler_hpp__ 1 - -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -#include "XMP_Environment.h" // ! This must be the first include. -#if ! ( XMP_64 || XMP_UNIXBuild) // Closes at very bottom. - -#include "XMPFiles_Impl.hpp" - -// Include these first to prevent collision with CIcon -#if XMP_WinBuild -#include "QTML.h" -#include "Movies.h" -#endif - -#if XMP_MacBuild -#include <Movies.h> -#endif - -// ================================================================================================= -/// \file MOV_Handler.hpp -/// \brief File format handler for MOV. -/// -/// This header ... -/// -// ================================================================================================= - -extern XMPFileHandler * MOV_MetaHandlerCTor ( XMPFiles * parent ); - -extern bool MOV_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef fileRef, - XMPFiles * parent ); - -static const XMP_OptionBits kMOV_HandlerFlags = (kXMPFiles_CanInjectXMP | - kXMPFiles_CanExpand | - kXMPFiles_PrefersInPlace | - kXMPFiles_AllowsOnlyXMP | - kXMPFiles_ReturnsRawPacket | - kXMPFiles_HandlerOwnsFile); - // In the future, we'll add kXMPFiles_CanReconcile - -class MOV_MetaHandler : public XMPFileHandler -{ -public: - - MOV_MetaHandler ( XMPFiles * parent ); - ~MOV_MetaHandler(); - - void CacheFileData(); - void ProcessXMP(); - - void UpdateFile ( bool doSafeUpdate ); - void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); - - struct CreatorAtomStrings { - std::string posixProjectPath; - std::string uncProjectPath; - std::string projectRefType; - std::string applicationCode; - std::string invocationAppleEvent; - std::string extension; - std::string invocationFlags; - std::string creatorTool; - }; - - CreatorAtomStrings mCreatorAtomStrings; // ! Public so utility code can muck with them. - -protected: - - bool mQTInit; - Handle mMovieDataRef; - DataHandler mMovieDataHandler; - Movie mMovie; - short mMovieResourceID; - long mFilePermission; - - bool OpenMovie ( long inPermission ); - void CloseMovie(); - -}; // MOV_MetaHandler - -// ================================================================================================= - -#endif // XMP_64 || XMP_UNIXBuild -#endif /* __MOV_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/MP3_Handler.cpp b/source/XMPFiles/FileHandlers/MP3_Handler.cpp index f906900..2c8f547 100644 --- a/source/XMPFiles/FileHandlers/MP3_Handler.cpp +++ b/source/XMPFiles/FileHandlers/MP3_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -8,100 +8,96 @@ // ================================================================================================= #include "XMP_Environment.h" // ! This must be the first include. -#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed. - #include "MP3_Handler.hpp" -#include "ID3_Support.hpp" - -using namespace std; - -#define mp3TitleChunk "TIT2" -#define mp3CreateDateChunk3 "TYER" -#define mp3CreateDateChunk4 "TDRV" -#define mp3ArtistChunk "TPE1" -#define mp3AlbumChunk "TALB" -#define mp3GenreChunk "TCON" -#define mp3CommentChunk "COMM" -#define mp3TrackChunk "TRCK" - -// DC -#define kTitle "title" - -// XMP -#define kCreateDate "CreateDate" - -// DM -#define kArtist "artist" -#define kAlbum "album" -#define kGenre "genre" -#define kLogComment "logComment" -#define kTrack "trackNumber" // ================================================================================================= /// \file MP3_Handler.cpp -/// \brief File format handler for MP3. -/// -/// This header ... -/// +/// \brief MP3 handler class. // ================================================================================================= // ================================================================================================= -// MP3_MetaHandlerCTor +// Helper structs and private routines // ==================== +struct ReconProps { + const char* frameID; + const char* ns; + const char* prop; +}; + +const static XMP_Uns32 XMP_FRAME_ID = 0x50524956; + +const static ReconProps reconProps[] = { + { "TPE1", kXMP_NS_DM, "artist" }, + { "TALB", kXMP_NS_DM, "album" }, + { "TRCK", kXMP_NS_DM, "trackNumber" }, + // exceptions that need attention: + { "TCON", kXMP_NS_DM, "genre" }, // genres might be numeric + { "TIT2", kXMP_NS_DC, "title" }, // ["x-default"] language alternatives + { "COMM", kXMP_NS_DM, "logComment" }, // two distinct strings, language alternative + + { "TYER", kXMP_NS_XMP, "CreateDate" }, // Year (YYYY) Deprecated since 2.4 + { "TDAT", kXMP_NS_XMP, "CreateDate" }, // Date (DDMM) Deprecated since 2.4 + { "TIME", kXMP_NS_XMP, "CreateDate" }, // Time (HHMM) Deprecated since 2.4 + { "TDRC", kXMP_NS_XMP, "CreateDate" }, // assemble date/time v2.4 + + // new reconciliations introduced in Version 5 + { "TCMP", kXMP_NS_DM, "partOfCompilation" }, // presence/absence of TCMP frame dedides + { "USLT", kXMP_NS_DM, "lyrics" }, + { "TCOM", kXMP_NS_DM, "composer" }, + { "TPOS", kXMP_NS_DM, "discNumber" }, // * a text field! might contain "/<total>" + { "TCOP", kXMP_NS_DC, "rights" }, // ["x-default"] language alternatives + { "TPE4", kXMP_NS_DM, "engineer" }, + { "WCOP", kXMP_NS_XMP_Rights , "WebStatement" }, + + { 0, 0, 0 } // must be last as a sentinel +}; +// ================================================================================================= +// MP3_MetaHandlerCTor +// ==================== XMPFileHandler * MP3_MetaHandlerCTor ( XMPFiles * parent ) { return new MP3_MetaHandler ( parent ); - -} // MP3_MetaHandlerCTor +} // ================================================================================================= // MP3_CheckFormat // =============== - // For MP3 we check parts .... See the MP3 spec for offset info. -// -// - bool MP3_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef inFileRef, - XMPFiles * parent ) + XMP_StringPtr filePath, + LFA_FileRef file, + XMPFiles * parent ) { - IgnoreParam(format); IgnoreParam(filePath); - XMP_Assert ( format == kXMP_MP3File ); + IgnoreParam(filePath); IgnoreParam(parent); //supress warnings + XMP_Assert ( format == kXMP_MP3File ); //standard assert + LFA_Rewind( file ); - if ( inFileRef == 0 ) return false; + XMP_Uns8 header[3]; + LFA_Read( file, header, 3, true ); + if ( !CheckBytes( &header[0], "ID3", 3 )) + return (parent->format == kXMP_MP3File); // No ID3 signature -> depend on first call hint. - // BUG FIX 1219125: If we find that the "unsynchronistation" flag is turned on, we should fail on that file. - // TODO: Support "unsynchronized" files. + XMP_Uns8 major = LFA_ReadUns8( file ); + XMP_Uns8 minor = LFA_ReadUns8( file ); - LFA_Seek ( inFileRef, 0ULL, SEEK_SET ); + if ( (major<3 || major>4) || (minor == 0xFF) ) + return false; // only support IDv2.3 and ID3v2.4, minor must not be 0xFF - char szID [4] = { "xxx" }; - long bytesRead = LFA_Read ( inFileRef, szID, 3 ); - if ( bytesRead != 3 ) return false; + XMP_Uns8 flags = LFA_ReadUns8( file ); - if ( strncmp ( szID, "ID3", 3 ) != 0 ) { - - return (parent->format == kXMP_MP3File); // No ID3 signature, depend on first call hint. - - } else { + //TODO + if ( flags & 0x10 ) + XMP_Throw("no support for MP3 with footer",kXMPErr_Unimplemented); //no support for MP3 with footer + if ( flags & 0x80 ) + return false; //no support for unsynchronized MP3 (as before, also see [1219125]) + if ( flags & 0x0F ) + XMP_Throw("illegal header lower bits",kXMPErr_Unimplemented); - // Read the version, flag and size. - XMP_Uns8 v2 = 0, flags = 0, bMajorVer = 0; - unsigned long dwLen = 0; - bool ok = ID3_Support::GetTagInfo ( inFileRef, bMajorVer, v2, flags, dwLen ); - - if ( ok ) { - if ( (bMajorVer < 3) || (bMajorVer > 4) ) return false; - if ( flags & 0x80 ) return false; // 0x80 == "unsynchronized" - } - - } + XMP_Uns32 size = LFA_ReadUns32_BE( file ); + if ( size & 0x80808080 ) return false; //if any bit survives -> not a valid synchsafe 32 bit integer return true; - } // MP3_CheckFormat @@ -114,8 +110,7 @@ MP3_MetaHandler::MP3_MetaHandler ( XMPFiles * _parent ) this->parent = _parent; this->handlerFlags = kMP3_HandlerFlags; this->stdCharForm = kXMP_Char8Bit; - -} // MP3_MetaHandler::MP3_MetaHandler +} // ================================================================================================= // MP3_MetaHandler::~MP3_MetaHandler @@ -123,224 +118,631 @@ MP3_MetaHandler::MP3_MetaHandler ( XMPFiles * _parent ) MP3_MetaHandler::~MP3_MetaHandler() { - // Nothing to do. - -} // MP3_MetaHandler::~MP3_MetaHandler - -// ================================================================================================= -// MP3_MetaHandler::UpdateFile -// =========================== - -void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) -{ - if ( ! this->needsUpdate ) return; - if ( doSafeUpdate ) XMP_Throw ( "MP3_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); - - bool fReconciliate = !(this->parent->openFlags & kXMPFiles_OpenOnlyXMP); - - XMP_StringPtr packetStr = xmpPacket.c_str(); - XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); - if ( packetLen == 0 ) return; - - LFA_FileRef fileRef ( this->parent->fileRef ); - if ( fileRef == 0 ) return; - - // Get the id3v2 version - XMP_Uns8 bVersion = 3; - unsigned long dwTagSize; - bool ok = ID3_Support::FindID3Tag ( fileRef, dwTagSize, bVersion ); - if ( ! ok ) bVersion = 3; - - // Allocate the temp buffer for the native frames we have to overwrite - unsigned long bufferSize = 7*TAG_MAX_SIZE; // Just enough buffer for all 7 tags - char buffer[7*TAG_MAX_SIZE]; - memset ( buffer, 0, bufferSize ); - unsigned long dwCurOffset = 0; - - if ( fReconciliate ) { - - std::string strTitle; - this->xmpObj.GetLocalizedText ( kXMP_NS_DC, kTitle, "", "x-default", 0, &strTitle, 0 ); - ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion, - mp3TitleChunk, strTitle.c_str(), (unsigned long)strTitle.size() ); - - std::string strDate; - this->xmpObj.GetProperty ( kXMP_NS_XMP, kCreateDate, &strDate, 0 ); - if ( bVersion == 4 ) { - ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion, - mp3CreateDateChunk4, strDate.c_str(), (unsigned long)strDate.size() ); - } else { - ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion, - mp3CreateDateChunk3, strDate.c_str(), (unsigned long)strDate.size() ); - } - - std::string strArtist; - this->xmpObj.GetProperty ( kXMP_NS_DM, kArtist, &strArtist, 0 ); - ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion, mp3ArtistChunk, - strArtist.c_str(), (unsigned long)strArtist.size() ); - - std::string strAlbum; - this->xmpObj.GetProperty ( kXMP_NS_DM, kAlbum, &strAlbum, 0 ); - ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion, - mp3AlbumChunk, strAlbum.c_str(), (unsigned long)strAlbum.size() ); - - std::string strGenre; - this->xmpObj.GetProperty ( kXMP_NS_DM, kGenre, &strGenre, 0 ); - ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion, - mp3GenreChunk, strGenre.c_str(), (unsigned long)strGenre.size() ); - - std::string strComment; - this->xmpObj.GetProperty ( kXMP_NS_DM, kLogComment, &strComment, 0 ); - ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion, - mp3CommentChunk, strComment.c_str(), (unsigned long)strComment.size() ); - - std::string strTrack; - this->xmpObj.GetProperty ( kXMP_NS_DM, kTrack, &strTrack, 0 ); - ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion, - mp3TrackChunk, strTrack.c_str(), (unsigned long)strTrack.size() ); - + // free frames + ID3v2Frame* curFrame; + while( !this->framesVector.empty() ) + { + curFrame = this->framesVector.back(); + delete curFrame; + framesVector.pop_back(); } - - // TODO id3v1 tags - - // Saving it all - ID3_Support::SetMetaData ( fileRef, (char*)packetStr, packetLen, buffer, dwCurOffset, fReconciliate ); - - this->needsUpdate = false; - -} // MP3_MetaHandler::UpdateFile - -// ================================================================================================= -// MP3_MetaHandler::WriteFile -// ========================== - -void MP3_MetaHandler::WriteFile ( LFA_FileRef sourceRef, - const std::string & sourcePath ) -{ - IgnoreParam(sourceRef); IgnoreParam(sourcePath); - - XMP_Throw ( "MP3_MetaHandler::WriteFile: Not supported", kXMPErr_Unavailable ); - -} // MP3_MetaHandler::WriteFile +} // ================================================================================================= // MP3_MetaHandler::CacheFileData // ============================== - void MP3_MetaHandler::CacheFileData() { - bool fReconciliate = ! ( this->parent->openFlags & kXMPFiles_OpenOnlyXMP ); - bool ok; + //*** abort procedures + this->containsXMP = false; //assume no XMP for now + + LFA_FileRef file = this->parent->fileRef; + XMP_PacketInfo &packetInfo = this->packetInfo; + + LFA_Rewind(file); + + hasID3Tag = id3Header.read( file ); + majorVersion = id3Header.fields[ID3Header::o_version_major]; + minorVersion = id3Header.fields[ID3Header::o_version_minor]; + hasExtHeader = (0 != ( 0x40 & id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag + hasFooter = ( 0 != ( 0x10 & id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag + + // stored size is w/o initial header (thus adding 10) + // + but extended header (if existing) + // + padding + frames after unsynchronisation (?) + // (if no ID3 tag existing, constructed default correctly sets size to 10.) + oldTagSize = 10 + synchToInt32(GetUns32BE( &id3Header.fields[ID3Header::o_size] )); + + if (hasExtHeader) + { + extHeaderSize = synchToInt32( LFA_ReadInt32_BE( file)); + XMP_Uns8 extHeaderNumFlagBytes = LFA_ReadUns8( file ); + + // v2.3 doesn't include the size, while v2.4 does + if ( majorVersion < 4 ) extHeaderSize += 4; + XMP_Validate( extHeaderSize >= 6, "extHeader size too small", kXMPErr_BadFileFormat ); + + bool ok; + LFA_Seek(file, extHeaderSize - 6, SEEK_CUR , &ok); + XMP_Assert(ok); + } + else + { + extHeaderSize = 0; // := there is no such header. + } - this->containsXMP = false; + this->framesVector.clear(); //mac precaution + ID3v2Frame* curFrame = 0; // reusable + + //////////////////////////////////////////////////// + // read frames + while ( LFA_Tell(file) < oldTagSize ) + { + curFrame = new ID3v2Frame(); + + try { + XMP_Int64 frameSize = curFrame->read( file, majorVersion ); + if (frameSize == 0) // no more frames coming => proceed to padding + { + delete curFrame; // ..since not becoming part of vector for latter delete. + break; // not a throw. There's nothing wrong with padding. + } + this->containsXMP = true; + } catch( XMP_Error e) + { + delete curFrame; + XMP_Throw( e.GetErrMsg(), e.GetID()); // rethrow + } - // We asked the host to allow us to manage the opening of the file - LFA_FileRef fileRef ( this->parent->fileRef ); - if ( fileRef == 0 ) return; + // these are both pointer assignments, no (copy) construction + // (MemLeak Note: for all things pushed, memory cleanup is taken care of in destructor.) + this->framesVector.push_back( curFrame ); + + //remember XMP-Frame, if it occurs: + if ( CheckBytes( &curFrame->fields[ID3v2Frame::o_id], "PRIV", 4 )) + if( curFrame->contentSize > 8 ) // to avoid adress violation on very small non-XMP PRIV frames + if( CheckBytes( &curFrame->content[0], "XMP\0", 4 )) + { + // be sure that this is the first packet (all else would be illegal format) + XMP_Validate( framesMap[ XMP_FRAME_ID] == 0, "two XMP packets in one file", kXMPErr_BadFileFormat ); + //add this to map, needed on reconciliation + framesMap[ XMP_FRAME_ID ] = curFrame; + + this->packetInfo.length = curFrame->contentSize - 4; // content minus "XMP\0" + this->packetInfo.offset = ( LFA_Tell(file) - this->packetInfo.length ); + + this->xmpPacket.erase(); //safety + this->xmpPacket.assign( &curFrame->content[4], curFrame->contentSize - 4 ); + this->containsXMP = true; // do this last, after all possible failure + } - // Determine the size of the metadata - unsigned long bufferSize(0); - ok = ID3_Support::GetMetaData ( fileRef, 0, &bufferSize, 0 ); + // No space for another frame? => assume into ID3v2.4 padding. + if ( LFA_Tell(file) + 10 >= oldTagSize ) + break; + } - if ( ! ok ) { + //////////////////////////////////////////////////// + // padding + oldPadding = oldTagSize - LFA_Tell( file ); + oldFramesSize = oldTagSize - 10 - oldPadding; + + XMP_Validate( oldPadding >= 0, "illegal oldTagSize or padding value", kXMPErr_BadFileFormat ); + + for ( XMP_Int64 i = oldPadding; i > 0;) + { + if ( i >= 8 ) // worthwhile optimization + { + if ( LFA_ReadInt64_BE(file) != 0 ) + XMP_Throw ( "padding not nulled out.", kXMPErr_BadFileFormat ); + i -= 8; + continue; + } + if ( LFA_ReadUns8(file) != 0) + XMP_Throw ( "padding(2) not nulled out.", kXMPErr_BadFileFormat ); + i--; + } - packetInfo.writeable = true; // If no packet found, created packets will be writeable + //// read ID3v1 tag + if ( ! this->containsXMP ) // all else has priority + { + this->containsXMP = id3v1Tag.read( file, &this->xmpObj ); + } - } else if ( bufferSize > 0 ) { +} // MP3_MetaHandler::CacheFileData - // Allocate the buffer - std::string buffer; - buffer.reserve ( bufferSize ); - buffer.assign ( bufferSize, ' ' ); - // Get the metadata - XMP_Int64 xmpOffset; - ok = ID3_Support::GetMetaData ( fileRef, (char*)buffer.c_str(), &bufferSize, &xmpOffset ); - if ( ok ) { - this->packetInfo.offset = xmpOffset; - this->packetInfo.length = bufferSize; - this->xmpPacket.assign(buffer.data(), bufferSize); - this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); - this->containsXMP = true; - } +// ================================================================================================= +// MP3_MetaHandler::ProcessXMP +// =========================== +// +// Process the raw XMP and legacy metadata that was previously cached. +void MP3_MetaHandler::ProcessXMP() +{ + // Process the XMP packet. + if ( ! this->xmpPacket.empty() ) { + XMP_Assert ( this->containsXMP ); + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen) this->xmpPacket.size(); + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + this->processedXMP = true; + } + /////////////////////////////////////////////////////////////////// + // assumptions on presence-absence "flag tags" + // ( unless no xmp whatsoever present ) + if ( ! this->xmpPacket.empty() ) { + this->xmpObj.SetProperty( kXMP_NS_DM, "partOfCompilation", "false" ); } - if ( fReconciliate ) { + //////////////////////////////////////////////////////////////////// + // import of legacy properties + ID3v2Frame* curFrame; + XMP_Bool hasTDRC = false; + XMP_DateTime newDateTime; + + if (this->hasID3Tag) // otherwise pretty pointless... + for (int r=0; reconProps[r].frameID != 0; r++) + { + //get the frame ID to look for + XMP_Uns32 frameID = GetUns32BE( reconProps[r].frameID ); + + // deal with each such frame in the frameVector + // (since there might be several, some of them not applicable, i.e. COMM) + for ( vector<ID3_Support::ID3v2Frame*>::iterator it = framesVector.begin(); it!=framesVector.end(); ++it) + { + curFrame = *it; + if (frameID != curFrame->id) // nothing applicable. Next! + continue; + + // go deal with it! + // get the property + std::string utf8string; + bool result = curFrame->getFrameValue(majorVersion, frameID, &utf8string); + + if (! result) + continue; //ignore but preserve this frame (i.e. not applicable COMM frame) + + ////////////////////////////////////////////////////////////////////////////////// + // if we come as far as here, it's proven that there's a relevant XMP property + this->containsXMP = true; + + ID3_Support::ID3v2Frame* t = framesMap[ frameID ]; + if ( t != 0 ) // an (earlier, relevant) frame? + t->active = false; + + // add this to map (needed on reconciliation) + // note: above code reaches, that COMM/USLT frames + // only then reach this map, if they are 'eng'(lish) + // multiple occurences indeed leads to last one survives + // ( in this map, all survive in the file ) + framesMap[ frameID ] = curFrame; + + // now write away as needed; + // merely based on existence, relevant even if empty: + if ( frameID == 0x54434D50) // TCMP if exists: part of compilation + { + this->xmpObj.SetProperty( kXMP_NS_DM, "partOfCompilation", "true" ); + } else if ( ! utf8string.empty() ) + switch( frameID ) + { + case 0x54495432: // TIT2 -> title["x-default"] + case 0x54434F50: // TCOP -> rights["x-default"] + this->xmpObj.SetLocalizedText( reconProps[r].ns , reconProps[r].prop,"" , "x-default" , utf8string ); + break; + case 0x54434F4E: // TCON -> genre ( might be numeric string. prior to 2.3 a one-byte numeric value? ) + { + XMP_Int32 pos = 0; // going through input string + if ( utf8string[pos] == '(' ) { // number in brackets? + pos++; + XMP_Uns8 iGenre = (XMP_Uns8) atoi( &utf8string.c_str()[1] ); + if ( (iGenre > 0) && (iGenre < 127) ) { + utf8string.assign( Genres[iGenre] ); + } else { + utf8string.assign( Genres[12] ); // "Other" + } + } else { + // Text, let's "try" to find it anyway (for best upper/lower casing) + int i; + const char* genreCString = utf8string.c_str(); + for ( i=0; i < 127; ++i ) { + if ( + (strlen( genreCString ) == strlen(Genres[i])) && //fixing buggy stricmp behaviour on PPC + (stricmp( genreCString, Genres[i] ) == 0 )) { + utf8string.assign( Genres[i] ); // found, let's use the one in the list + break; + } + } + // otherwise (if for-loop runs through): leave as is + } + // write out property (derived or direct, but certainly non-numeric) + this->xmpObj.SetProperty( reconProps[r].ns, reconProps[r].prop, utf8string ); + } + break; + case 0x54594552: // TYER -> xmp:CreateDate.year + { + try { // Don't let wrong dates in id3 stop import. + if ( !hasTDRC ) + { + newDateTime.year = SXMPUtils::ConvertToInt( utf8string ); + newDateTime.hasDate = true; + } + } catch ( ... ) { + // Do nothing, let other imports proceed. + } + break; + } + case 0x54444154: //TDAT -> xmp:CreateDate.month and day + { + try { // Don't let wrong dates in id3 stop import. + // only if no TDRC has been found before + //&& must have the format DDMM + if ( !hasTDRC && utf8string.length() == 4 ) + { + newDateTime.day = SXMPUtils::ConvertToInt(utf8string.substr(0,2)); + newDateTime.month = SXMPUtils::ConvertToInt( utf8string.substr(2,2)); + newDateTime.hasDate = true; + } + } catch ( ... ) { + // Do nothing, let other imports proceed. + } + break; + } + case 0x54494D45: //TIME -> xmp:CreateDate.hours and minutes + { + try { // Don't let wrong dates in id3 stop import. + // only if no TDRC has been found before + // && must have the format HHMM + if ( !hasTDRC && utf8string.length() == 4 ) + { + newDateTime.hour = SXMPUtils::ConvertToInt(utf8string.substr(0,2)); + newDateTime.minute = SXMPUtils::ConvertToInt( utf8string.substr(2,2)); + newDateTime.hasTime = true; + } + } catch ( ... ) { + // Do nothing, let other imports proceed. + } + break; + } + case 0x54445243: // TDRC -> xmp:CreateDate //id3 v2.4 + { + try { // Don't let wrong dates in id3 stop import. + hasTDRC = true; + // This always wins over TYER, TDAT and TIME + SXMPUtils::ConvertToDate( utf8string, &newDateTime ); + } catch ( ... ) { + // Do nothing, let other imports proceed. + } + break; + } + default: + // NB: COMM/USLT need no special fork regarding language alternatives/multiple occurence. + // relevant code forks are in ID3_Support::getFrameValue() + this->xmpObj.SetProperty( reconProps[r].ns, reconProps[r].prop, utf8string ); + break; + }//switch + } //for iterator + }//for reconProps + + // import DateTime + XMP_DateTime oldDateTime; + xmpObj.GetProperty_Date( kXMP_NS_XMP, "CreateDate", &oldDateTime, 0 ); - // ! Note that LoadPropertyFromID3 sets this->containsXMP, update this->processedXMP after! - LoadPropertyFromID3 ( fileRef, mp3TitleChunk, kXMP_NS_DC, kTitle, true ); - LoadPropertyFromID3 ( fileRef, mp3CreateDateChunk3, kXMP_NS_XMP, kCreateDate ); - LoadPropertyFromID3 ( fileRef, mp3ArtistChunk, kXMP_NS_DM, kArtist ); - LoadPropertyFromID3 ( fileRef, mp3AlbumChunk, kXMP_NS_DM, kAlbum ); - LoadPropertyFromID3 ( fileRef, mp3GenreChunk, kXMP_NS_DM, kGenre ); - LoadPropertyFromID3 ( fileRef, mp3CommentChunk, kXMP_NS_DM, kLogComment ); - LoadPropertyFromID3 ( fileRef, mp3TrackChunk, kXMP_NS_DM, kTrack ); + + // NOTE: no further validation nessesary the function "SetProperty_Date" will care about validating date and time + // any exception will be caught and block import + try { + // invalid year will be catched and blocks import + XMP_Validate( (newDateTime.year > 0 && newDateTime.year < 9999), "", kXMPErr_BadParam ); + + // 2. if year has changed --> everything (date/time) has changed --> overwrite old DateTime with new DateTime + if ( ( newDateTime.year != oldDateTime.year ) || // year has changed? + // or has same year but new day/month (checking existance month indicates both (day and month) in our case) + (( newDateTime.month != 0 ) && ( newDateTime.day != oldDateTime.day || newDateTime.month != oldDateTime.month )) || + // or has same year and same date but different time + ( newDateTime.hasTime && ( newDateTime.hour != oldDateTime.minute || newDateTime.hour != oldDateTime.minute )) ) + { + this->xmpObj.SetProperty_Date( kXMP_NS_XMP, "CreateDate", newDateTime ); + } // ..else: keep old dateTime to don't loose data + + } catch ( ... ) { + // Dont import invalid dates from ID3 } - - this->processedXMP = this->containsXMP; -} // MP3_MetaHandler::CacheFileData -// ================================================================================================= + // very important to avoid multiple runs! (in which case I'd need to clean certain + // fields (i.e. regarding ->active setting) + this->processedXMP = true; -bool MP3_MetaHandler::LoadPropertyFromID3 ( LFA_FileRef inFileRef, char * strFrame, char * strNameSpace, char * strXMPTag, bool fLocalText ) -{ +} // MP3_MetaHandler::ProcessXMP - // Allocate the temp buffer for the native frames we have to overwrite - unsigned long bufferSize = TAG_MAX_SIZE; - std::string buffer; - buffer.reserve ( bufferSize ); - buffer.assign ( bufferSize, '\0' ); - - // Get the old XMP tag - std::string xmpString(""); - if ( fLocalText ) { - this->xmpObj.GetLocalizedText ( strNameSpace, strXMPTag, "", "x-default", 0, &xmpString, 0 ); - } else { - this->xmpObj.GetProperty ( strNameSpace, strXMPTag, &xmpString, 0 ); - } - // Get the frame - bool ok = ID3_Support::GetFrameData ( inFileRef, strFrame, (char*)buffer.c_str(), bufferSize ); - if ( ok ) { - if ( ! buffer.empty() ) { +// ================================================================================================= +// MP3_MetaHandler::UpdateFile +// =========================== +void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( doSafeUpdate ) + XMP_Throw ( "UCF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); + + LFA_FileRef file ( this->parent->fileRef ); + + // leave 2.3 resp. 2.4 header, since we want to let alone + // and don't know enough about the encoding of unrelated frames... + XMP_Assert( this->containsXMP ); + + tagIsDirty = false; + mustShift = false; + + // write out native properties: + // * update existing ones + // * create new frames as needed + // * delete frames if property is gone! + // see what there is to do for us: + + // RECON LOOP START + for (int r=0; reconProps[r].frameID != 0; r++) + { + std::string value; + bool needDescriptor = false; + bool need16LE = true; + bool needEncodingByte = true; + + XMP_Uns32 frameID = GetUns32BE( reconProps[r].frameID ); // the would-be frame + ID3v2Frame* frame = framesMap[ frameID ]; // the actual frame (if already existing) + + // get XMP property + // * honour specific exceptions + // * leave value empty() if it doesn't exist ==> frame must be delete/not created + switch( frameID ) + { + case 0x54434D50: // TCMP if exists: part of compilation + need16LE = false; + if ( xmpObj.GetProperty( kXMP_NS_DM, "partOfCompilation", &value, 0 ) + && ( 0 == stricmp( value.c_str(), "true" ) )) + value = "1"; // set a TCMP frame of value 1 + else + value.erase(); // delete/prevent creation of frame + break; + + case 0x54495432: // TIT2 -> title["x-default"] + case 0x54434F50: // TCOP -> rights["x-default"] + if (! xmpObj.GetLocalizedText( reconProps[r].ns, reconProps[r].prop, "", "x-default", 0, &value, 0 )) //jaja, side effect. + value.erase(); // if not, erase string. + break; + case 0x54434F4E: // TCON -> genre + { + if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) + { // nothing found? -> Erase string. (Leads to Unset below) + value.erase(); + break; + } + // genre: we need to get the number back, if possible + XMP_Int16 iFound = -1; // flag as "not found" + for ( int i=0; i < 127; ++i ) { + if ( (value.size() == strlen(Genres[i])) + && (stricmp( value.c_str(), Genres[i] ) == 0) ) //fixing stricmp buggy on PPC + { + iFound = i; // Found + break; + } + } + if ( iFound == -1 ) // not found known numeric genre? + break; // stick with the literal value (also for v2.3, since this is common practice!) - if ( xmpString.compare ( buffer ) ) { - if ( fLocalText ) { - this->xmpObj.SetLocalizedText ( strNameSpace, strXMPTag, 0, "x-default", buffer ); - } else { - this->xmpObj.SetProperty ( strNameSpace, strXMPTag, buffer, 0 ); + need16LE = false; // no unicode need for (##) + char strGenre[64]; + snprintf ( strGenre, sizeof(strGenre), "(%d)", iFound ); // AUDIT: Using sizeof(strGenre) is safe. + value.assign(strGenre); + } + break; + case 0x434F4D4D: // COMM + case 0x55534C54: // USLT, both need descriptor. + needDescriptor = true; + if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) + value.erase(); + break; + case 0x54594552: //TYER + case 0x54444154: //TDAT + case 0x54494D45: //TIME + { + if ( majorVersion <= 3 ) // TYER, TIME and TDAT depricated since v. 2.4 -> else use TDRC + { + XMP_DateTime dateTime; + if (! xmpObj.GetProperty_Date( reconProps[r].ns, reconProps[r].prop, &dateTime, 0 )) + { // nothing found? -> Erase string. (Leads to Unset below) + value.erase(); + break; + } + + // TYER + if ( frameID == 0x54594552 ) + { + XMP_Validate( dateTime.year <= 9999 && dateTime.year > 0 , "Year is out of range", kXMPErr_BadParam); + // get only Year! + SXMPUtils::ConvertFromInt( dateTime.year, "", &value ); + break; + } + // TDAT + else if ( frameID == 0x54444154 && dateTime.hasDate ) // date validation made by "GetProperty_Date" + { + std::string day, month; + SXMPUtils::ConvertFromInt( dateTime.day, "", &day ); + SXMPUtils::ConvertFromInt( dateTime.month, "", &month ); + if ( dateTime.day < 10 ) + value = "0"; + value += day; + if ( dateTime.month < 10 ) + value += "0"; + value += month; + break; + } + // TIME + else if ( frameID == 0x54494D45 && dateTime.hasTime ) // time validation made by "GetProperty_Date" ) + { + std::string hour, minute; + SXMPUtils::ConvertFromInt( dateTime.hour, "", &hour ); + SXMPUtils::ConvertFromInt( dateTime.minute, "", &minute ); + if ( dateTime.hour < 10 ) + value = "0"; + value += hour; + if ( dateTime.minute < 10 ) + value += "0"; + value += minute; + break; + } + else + { + value.erase(); + break; + } } + else // v.2.4 --> delete TYER,TIME or TDAT & write into TDRC + { + value.erase(); + break; + } + } + case 0x54445243: //TDRC (only v2.4) + { + // only export for id3 > v2.4 + if ( majorVersion > 3 ) // (only v2.4) + { + if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) + value.erase(); + } + break; } + break; + case 0x57434F50: //WCOP + needEncodingByte = false; + need16LE = false; + if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) + value.erase(); // if not, erase string + break; + case 0x5452434B: // TRCK + case 0x54504F53: // TPOS + need16LE = false; + // no break, go on: + default: + if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) + value.erase(); // if not, erase string + break; + } - this->containsXMP = true; - return true; + // [XMP exist] x [frame exist] => four cases: + // 1/4) nothing before, nothing now + if ( value.empty() && (frame==0)) + continue; // nothing to do + + // all else means there will be rewrite work to do: + tagIsDirty = true; + // 2/4) value before, now gone: + if ( value.empty() && (frame!=0)) + { + frame->active = false; //mark for non-use + continue; + } + // 3/4) no old value, create new value + if ( frame==0) + { + ID3v2Frame* newFrame=new ID3v2Frame( frameID ); + newFrame->setFrameValue( value, needDescriptor, need16LE, false, needEncodingByte ); //always write as utf16-le incl. BOM + framesVector.push_back( newFrame ); + framesMap[ frameID ] = newFrame; + continue; } + // 4/4) change existing value + else // resp. change frame + { + frame->setFrameValue( value, needDescriptor, need16LE, false, needEncodingByte ); + } + } // RECON LOOP END + + ///////////////////////////////////////////////////////////////////////////////// + // (Re)Build XMP frame: + ID3v2Frame* frame = framesMap[ XMP_FRAME_ID ]; + if ( frame == 0) + { + ID3v2Frame* newFrame=new ID3v2Frame( XMP_FRAME_ID ); + newFrame->setFrameValue( this->xmpPacket, false, false, true ); + framesVector.push_back( newFrame ); + framesMap[ XMP_FRAME_ID ] = newFrame; + } else + frame->setFrameValue( this->xmpPacket, false, false, true ); + + //////////////////////////////////////////////////////////////////////////////// + // Decision making + newFramesSize = 0; + for ( XMP_Uns32 i=0; i < framesVector.size(); i++) + { + if (framesVector[i]->active) + newFramesSize += (10 + framesVector[i]->contentSize ); } - // Couldn't find the frame, let's clean it on the XMP side if that tag exists. - if ( xmpString.size() != 0 ) { - - buffer = ""; + mustShift = ( newFramesSize > (oldTagSize - 10)) || + //optimization: If more than 8K can be saved by rewriting the MP3, go do it: + ((newFramesSize + 8*1024) < oldTagSize ); + + if (!mustShift) // fill what we got + newTagSize = oldTagSize; + else // if need to shift anyway, get some nice 2K padding + newTagSize = newFramesSize + 2048 + 10; + newPadding = newTagSize -10 - newFramesSize; + + // shifting needed? -> shift + if ( mustShift ) + { + XMP_Int64 filesize = LFA_Measure( file ); + if ( this->hasID3Tag ) + LFA_Move( file, oldTagSize, file, newTagSize , filesize - oldTagSize ); //fix [2338569] + else + LFA_Move( file, 0, file, newTagSize, filesize ); // move entire file up. + } - if ( fLocalText ) { - this->xmpObj.SetLocalizedText ( strNameSpace, strXMPTag, 0, "x-default", buffer ); - } else { - this->xmpObj.SetProperty ( strNameSpace, strXMPTag, buffer, 0 ); - } + // correct size stuff, write out header + LFA_Rewind( file ); + id3Header.write( file, newTagSize); - this->containsXMP = true; - return true; + // write out tags + for ( XMP_Uns32 i=0; i < framesVector.size(); i++) + { + if ( framesVector[i]->active) + framesVector[i]->write(file, majorVersion); + } + // write out padding: + for ( XMP_Int64 i = newPadding; i > 0;) + { + const XMP_Uns64 zero = 0; + if ( i >= 8 ) // worthwhile optimization + { + LFA_Write( file, &zero, 8 ); + i -= 8; + continue; + } + LFA_Write( file, &zero, 1 ); + i--; } - return false; + // check end of file for ID3v1 tag + XMP_Int64 possibleTruncationPoint = LFA_Seek( file, -128, SEEK_END); + bool alreadyHasID3v1 = (LFA_ReadInt32_BE( file ) & 0xFFFFFF00) == 0x54414700; // "TAG" + if ( ! alreadyHasID3v1 ) // extend file + LFA_Extend( file, LFA_Measure( file ) + 128 ); + id3v1Tag.write( file, &this->xmpObj ); -} // WAV_MetaHandler::LoadPropertyFromID3 + this->needsUpdate = false; //do last for safety reasons +} // MP3_MetaHandler::UpdateFile // ================================================================================================= +// MP3_MetaHandler::WriteFile +// ========================== -#endif // XMP_UNIXBuild +void MP3_MetaHandler::WriteFile ( LFA_FileRef sourceRef, + const std::string & sourcePath ) +{ + IgnoreParam(sourceRef); IgnoreParam(sourcePath); + XMP_Throw ( "MP3_MetaHandler::WriteFile: Not supported", kXMPErr_Unimplemented ); +} // MP3_MetaHandler::WriteFile diff --git a/source/XMPFiles/FileHandlers/MP3_Handler.hpp b/source/XMPFiles/FileHandlers/MP3_Handler.hpp index 8a321e6..d6be9cf 100644 --- a/source/XMPFiles/FileHandlers/MP3_Handler.hpp +++ b/source/XMPFiles/FileHandlers/MP3_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -11,17 +11,11 @@ // ================================================================================================= #include "XMP_Environment.h" // ! This must be the first include. -#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed. - #include "XMPFiles_Impl.hpp" +#include "ID3_Support.hpp" -// ================================================================================================= -/// \file MP3_Handler.hpp -/// \brief File format handler for MP3. -/// -/// This header ... -/// -// ================================================================================================= +using namespace std; +using namespace ID3_Support; extern XMPFileHandler * MP3_MetaHandlerCTor ( XMPFiles * parent ); @@ -34,13 +28,12 @@ static const XMP_OptionBits kMP3_HandlerFlags = (kXMPFiles_CanInjectXMP | kXMPFiles_CanExpand | kXMPFiles_PrefersInPlace | kXMPFiles_AllowsOnlyXMP | - kXMPFiles_ReturnsRawPacket); - // In the future, we'll add kXMPFiles_CanReconcile + kXMPFiles_ReturnsRawPacket| + kXMPFiles_CanReconcile); class MP3_MetaHandler : public XMPFileHandler { public: - MP3_MetaHandler ( XMPFiles * parent ); ~MP3_MetaHandler(); @@ -48,13 +41,52 @@ public: void UpdateFile ( bool doSafeUpdate ); void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); + void ProcessXMP(); private: - bool LoadPropertyFromID3(LFA_FileRef inFileRef, char *strFrame, char *strNameSpace, char *strXMPTag, bool fLocalText = false); + //////////////////////////////////////////////////////////////////////////////////// + // instance vars + XMP_Int64 oldTagSize; // the entire tag, including padding, including 10B header + XMP_Int64 oldPadding; // number of padding bytes + XMP_Int64 oldFramesSize; // actual space needed by frames := oldTagSize - 10 - oldPadding + + XMP_Int64 newTagSize; + XMP_Int64 newPadding; + XMP_Int64 newFramesSize; + + // decision making: + bool tagIsDirty; // true, if any legacy properties changed. + bool mustShift; // entire tag to rewrite? (or possibly just XMP?) + + + XMP_Uns8 majorVersion, minorVersion; // Version Number post ID3v2, i.e. 3 0 ==> ID3v2.3.0 + bool hasID3Tag; //incoming file has an ID3 tag? + bool hasFooter; + bool legacyChanged; // tag rewrite certainly needed? + + ID3Header id3Header; + + XMP_Int64 extHeaderSize; + bool hasExtHeader; + + // the frames + // * all to be kept till write-out + // * parsed/understood only if needed + // * vector used to free memory in handler destructor + std::vector<ID3_Support::ID3v2Frame*> framesVector; + + // ID3v1 - treated as a single object + ID3v1Tag id3v1Tag; + + // * also kept in a map for better import<->export access + // * only keeps legacy 'relevant frames' (i.e. no abused COMM frames) + // * only keeps last relevant frame + // * earlier 'relevant frames' will be deleted. This map also helps in this + // * key shall be the FrameID, always interpreted as BE + std::map<XMP_Uns32,ID3_Support::ID3v2Frame*> framesMap; }; // MP3_MetaHandler // ================================================================================================= -#endif // XMP_UNIXBuild #endif /* __MP3_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/MPEG2_Handler.cpp b/source/XMPFiles/FileHandlers/MPEG2_Handler.cpp index 9c2eb48..438e705 100644 --- a/source/XMPFiles/FileHandlers/MPEG2_Handler.cpp +++ b/source/XMPFiles/FileHandlers/MPEG2_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2005 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/MPEG2_Handler.hpp b/source/XMPFiles/FileHandlers/MPEG2_Handler.hpp index 8bb0464..96ef407 100644 --- a/source/XMPFiles/FileHandlers/MPEG2_Handler.hpp +++ b/source/XMPFiles/FileHandlers/MPEG2_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2005 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp b/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp index a3f92d6..06400d1 100644 --- a/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp +++ b/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -8,6 +8,8 @@ // ================================================================================================= #include "MPEG4_Handler.hpp" +#include "ISOBaseMedia_Support.hpp" +#include "MOOV_Support.hpp" #include "UnicodeConversions.hpp" #include "MD5.h" @@ -26,36 +28,46 @@ using namespace std; /// // ================================================================================================= -// File and box type codes as big endian 32-bit integers, allows faster comparisons. +// The basic content of a timecode sample description table entry. Does not include trailing boxes. -static XMP_Uns32 kBE_ftyp = MakeUns32BE ( 0x66747970UL ); // File header Box, no version/flags. +#pragma pack ( push, 1 ) -static XMP_Uns32 kBE_mp41 = MakeUns32BE ( 0x6D703431UL ); // Compatibility codes -static XMP_Uns32 kBE_mp42 = MakeUns32BE ( 0x6D703432UL ); -static XMP_Uns32 kBE_f4v = MakeUns32BE ( 0x66347620UL ); +struct stsdBasicEntry { + XMP_Uns32 entrySize; + XMP_Uns32 format; + XMP_Uns8 reserved_1 [6]; + XMP_Uns16 dataRefIndex; + XMP_Uns32 reserved_2; + XMP_Uns32 flags; + XMP_Uns32 timeScale; + XMP_Uns32 frameDuration; + XMP_Uns8 frameCount; + XMP_Uns8 reserved_3; +}; -static XMP_Uns32 kBE_moov = MakeUns32BE ( 0x6D6F6F76UL ); // Container Box, no version/flags. -static XMP_Uns32 kBE_mvhd = MakeUns32BE ( 0x6D766864UL ); // Data FullBox, has version/flags. -static XMP_Uns32 kBE_udta = MakeUns32BE ( 0x75647461UL ); // Container Box, no version/flags. -static XMP_Uns32 kBE_cprt = MakeUns32BE ( 0x63707274UL ); // Data FullBox, has version/flags. -static XMP_Uns32 kBE_uuid = MakeUns32BE ( 0x75756964UL ); // Data Box, no version/flags. -static XMP_Uns32 kBE_free = MakeUns32BE ( 0x66726565UL ); // Free space Box, no version/flags. -static XMP_Uns32 kBE_mdat = MakeUns32BE ( 0x6D646174UL ); // Media data Box, no version/flags. +#pragma pack ( pop ) -static XMP_Uns32 kBE_xmpUUID [4] = { MakeUns32BE ( 0xBE7ACFCBUL ), - MakeUns32BE ( 0x97A942E8UL ), - MakeUns32BE ( 0x9C719994UL ), - MakeUns32BE ( 0x91E3AFACUL ) }; +// ================================================================================================= // ! The buffer and constants are both already big endian. #define Get4CharCode(buffPtr) (*((XMP_Uns32*)(buffPtr))) // ================================================================================================= +static inline bool IsClassicQuickTimeBox ( XMP_Uns32 boxType ) +{ + if ( (boxType == ISOMedia::k_moov) || (boxType == ISOMedia::k_mdat) || (boxType == ISOMedia::k_pnot) || + (boxType == ISOMedia::k_free) || (boxType == ISOMedia::k_skip) || (boxType == ISOMedia::k_wide) ) return true; + return false; +} // IsClassicQuickTimeBox + +// ================================================================================================= + // Pairs of 3 letter ISO 639-2 codes mapped to 2 letter ISO 639-1 codes from: // http://www.loc.gov/standards/iso639-2/php/code_list.php // Would have to write an "==" operator to use std::map, must compare values not pointers. // ! Not fully sorted, do not use a binary search. + static XMP_StringPtr kKnownLangs[] = { "aar", "aa", "abk", "ab", "afr", "af", "aka", "ak", "alb", "sq", "sqi", "sq", "amh", "am", "ara", "ar", "arg", "an", "arm", "hy", "hye", "hy", "asm", "as", "ava", "av", "ave", "ae", @@ -92,91 +104,161 @@ static XMP_StringPtr kKnownLangs[] = "tib", "bo", "bod", "bo", "tir", "ti", "ton", "to", "tsn", "tn", "tso", "ts", "tuk", "tk", "tur", "tr", "twi", "tw", "uig", "ug", "ukr", "uk", "urd", "ur", "uzb", "uz", "ven", "ve", "vie", "vi", "vol", "vo", "wel", "cy", "cym", "cy", "wln", "wa", "wol", "wo", "xho", "xh", - "yid", "yi", "yor", "yo", "zha", "za", "zho", "zh", "chi", "zh", "zul", "zu", + "yid", "yi", "yor", "yo", "zha", "za", "zho", "zh", "chi", "zh", "zul", "zu", 0, 0 }; -static XMP_StringPtr Lookup2LetterLang ( XMP_StringPtr lang3 ) +static inline XMP_StringPtr Lookup2LetterLang ( XMP_StringPtr lang3 ) { - for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) { if ( XMP_LitMatch ( lang3, kKnownLangs[i] ) ) return kKnownLangs[i+1]; } - - return lang3; // Not found, return the input. - -} // Lookup2LetterLang + return ""; +} + +static inline XMP_StringPtr Lookup3LetterLang ( XMP_StringPtr lang2 ) +{ + for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) { + if ( XMP_LitMatch ( lang2, kKnownLangs[i+1] ) ) return kKnownLangs[i]; + } + return ""; +} // ================================================================================================= // MPEG4_CheckFormat // ================= // -// An MPEG-4 file is an instance of an ISO Base Media file, ISO 14496-12 and -14. An MPEG-4 file -// must begin with an 'ftyp' box containing 'mp41', 'mp42', or 'f4v ' in the compatible brands. +// There are 3 variations of recognized file: +// - Normal MPEG-4 - has an 'ftyp' box containing a known compatible brand but not 'qt '. +// - Modern QuickTime - has an 'ftyp' box containing 'qt ' as a compatible brand. +// - Classic QuickTime - has no 'ftyp' box, should have recognized top level boxes. +// +// An MPEG-4 or modern QuickTime file is an instance of an ISO Base Media file, ISO 14496-12 and -14. +// A classic QuickTime file has the same physical box structure, but somewhat different box types. +// The ISO files must begin with an 'ftyp' box containing 'mp41', 'mp42', 'f4v ', or 'qt ' in the +// compatible brands. +// +// The general box structure is: // -// The 'ftyp' box structure is: +// 0 4 uns32 box size, 0 means "to EoF", 1 means 64-bit size follows +// 4 4 uns32 box type +// 8 8 uns64 box size, present only if uns32 size is 1 +// - * box content +// +// The 'ftyp' box content is: // -// 0 4 uns32 box size, 0 means "to EoF" -// 4 4 uns32 box type, 'ftyp' -// 8 8 uns64 box size, if uns32 size is 1 // - 4 uns32 major brand // - 4 uns32 minor version // - * uns32 sequence of compatible brands, to the end of the box -// -// All integers are big endian. + +// ! With the addition of QuickTime support there is some change in behavior in OpenFile when the +// ! kXMPFiles_OpenStrictly option is used wth a specific file format. The kXMP_MPEG4File and +// ! kXMP_MOVFile formats are treated uniformly, you can't force "real MOV" or "real MPEG-4". You +// ! can check afterwards using GetFileInfo to see what the file happens to be. bool MPEG4_CheckFormat ( XMP_FileFormat format, XMP_StringPtr filePath, LFA_FileRef fileRef, XMPFiles* parent ) { - XMP_Uns8 buffer [4096]; - XMP_Uns32 ioCount, brandCount, brandOffset, id; - XMP_Uns64 fileSize, boxSize; - - // Do some basic header checks, figure out how many compatible brands are listed. - + XMP_Uns8 buffer [4*1024]; + XMP_Uns32 ioCount, brandCount, brandOffset; + XMP_Uns64 fileSize, nextOffset; + ISOMedia::BoxInfo currBox; + + #define IsTolerableBoxChar(ch) ( ((0x20 <= (ch)) && ((ch) <= 0x7E)) || ((ch) == 0xA9) ) + + XMP_AbortProc abortProc = parent->abortProc; + void * abortArg = parent->abortArg; + const bool checkAbort = (abortProc != 0); + + bool openStrictly = XMP_OptionIsSet ( parent->openFlags, kXMPFiles_OpenStrictly); + + // Get the first box's info, see if it is 'ftyp' or not. + + XMP_Assert ( (parent->tempPtr == 0) && (parent->tempUI32 == 0) ); + fileSize = LFA_Measure ( fileRef ); - - LFA_Seek ( fileRef, 0, SEEK_SET ); - ioCount = LFA_Read ( fileRef, buffer, 16 ); // Read to the compatible brands, assuming a 32-bit size. - if ( ioCount < 16 ) return false; + nextOffset = ISOMedia::GetBoxInfo ( fileRef, 0, fileSize, &currBox ); + if ( currBox.headerSize < 8 ) return false; // Can't be an ISO or QuickTime file. - id = Get4CharCode ( &buffer[4] ); // Is the first box an 'ftyp' box? - if ( id != kBE_ftyp ) return false; - - boxSize = GetUns32BE ( &buffer[0] ); // Get the box length. - brandOffset = 16; + if ( currBox.boxType == ISOMedia::k_ftyp ) { - if ( boxSize == 0 ) { - boxSize = fileSize; - } else if ( boxSize == 1 ) { - boxSize = GetUns64BE ( &buffer[8] ); - LFA_Seek ( fileRef, 24, SEEK_SET ); // Seek to the compatible brands. - brandOffset = 24; - } - - if ( (boxSize < brandOffset) || (boxSize > fileSize) || - ((boxSize & 0x3) != 0) || (boxSize > 4024) ) return false; // Sanity limit of 1000 brands. + // Have an 'ftyp' box, look through the compatible brands. If 'qt ' is present then this is + // a modern QuickTime file, regardless of what else is found. Otherwise this is plain ISO if + // any of the other recognized brands are found. + + if ( currBox.contentSize < 12 ) return false; // No compatible brands at all. + if ( currBox.contentSize > 1024*1024 ) return false; // Sanity check and make sure count fits in 32 bits. + brandCount = ((XMP_Uns32)currBox.contentSize - 8) >> 2; + + LFA_Seek ( fileRef, 8, SEEK_CUR ); // Skip the major and minor brands. + ioCount = brandOffset = 0; + + bool haveCompatibleBrand = false; + + for ( ; brandCount > 0; --brandCount, brandOffset += 4 ) { + + if ( brandOffset >= ioCount ) { + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_CheckFormat - User abort", kXMPErr_UserAbort ); + } + ioCount = 4 * brandCount; + if ( ioCount > sizeof(buffer) ) ioCount = sizeof(buffer); + ioCount = LFA_Read ( fileRef, buffer, ioCount, kLFA_RequireAll ); + brandOffset = 0; + } + + XMP_Uns32 brand = GetUns32BE ( &buffer[brandOffset] ); + if ( brand == ISOMedia::k_qt ) { // Don't need to look further. + if ( openStrictly && (format != kXMP_MOVFile) ) return false; + parent->format = kXMP_MOVFile; + parent->tempUI32 = MOOV_Manager::kFileIsModernQT; + return true; + } else if ( (brand == ISOMedia::k_mp41) || (brand == ISOMedia::k_mp42) || (brand == ISOMedia::k_f4v) ) { + haveCompatibleBrand = true; // Need to keep looking in case 'qt ' follows. + } - // Look for the 'mp41', 'mp42', of 'f4v ' compatible brands. - - brandCount = (XMP_Uns32)( (boxSize - brandOffset) / 4 ); - while ( brandCount > 0 ) { - - XMP_Uns32 clumpSize = brandCount * 4; - if ( clumpSize > sizeof(buffer) ) clumpSize = sizeof(buffer); - ioCount = LFA_Read ( fileRef, buffer, clumpSize ); - if ( ioCount < clumpSize ) return false; - - for ( XMP_Uns32 i = 0; i < clumpSize; i += 4 ) { - id = Get4CharCode ( &buffer[i] ); - if ( (id == kBE_mp41) || (id == kBE_mp42) || (id == kBE_f4v) ) return true; } - - brandCount -= clumpSize/4; + + if ( ! haveCompatibleBrand ) return false; + if ( openStrictly && (format != kXMP_MPEG4File) ) return false; + parent->format = kXMP_MPEG4File; + parent->tempUI32 = MOOV_Manager::kFileIsNormalISO; + return true; + + } else { + + // No 'ftyp', look for classic QuickTime: 'moov', 'mdat', 'pnot', 'free', 'skip', and 'wide'. + // As an expedient, quit when a 'moov' box is found. Tolerate other boxes, they are in the + // wild for ill-formed files, e.g. seen when 'moov'/'udta' children get left at top level. + + while ( currBox.boxType != ISOMedia::k_moov ) { + + if ( ! IsClassicQuickTimeBox ( currBox.boxType ) ) { + // Make sure the box type is 4 ASCII characters or 0xA9 (MacRoman copyright). + XMP_Uns8 b1 = (XMP_Uns8) (currBox.boxType >> 24); + XMP_Uns8 b2 = (XMP_Uns8) ((currBox.boxType >> 16) & 0xFF); + XMP_Uns8 b3 = (XMP_Uns8) ((currBox.boxType >> 8) & 0xFF); + XMP_Uns8 b4 = (XMP_Uns8) (currBox.boxType & 0xFF); + bool ok = IsTolerableBoxChar(b1) && IsTolerableBoxChar(b2) && + IsTolerableBoxChar(b3) && IsTolerableBoxChar(b4); + if ( ! ok ) return false; + } + if ( nextOffset >= fileSize ) return false; + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_CheckFormat - User abort", kXMPErr_UserAbort ); + } + nextOffset = ISOMedia::GetBoxInfo ( fileRef, nextOffset, fileSize, &currBox ); + + } + + if ( openStrictly && (format != kXMP_MOVFile) ) return false; + parent->format = kXMP_MOVFile; + parent->tempUI32 = MOOV_Manager::kFileIsTraditionalQT; + return true; } - + return false; } // MPEG4_CheckFormat @@ -196,12 +278,15 @@ XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent ) // MPEG4_MetaHandler::MPEG4_MetaHandler // ==================================== -MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent ) : xmpBoxPos(0) +MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent ) + : fileMode(0), havePreferredXMP(false), xmpBoxPos(0), moovBoxPos(0), xmpBoxSize(0), moovBoxSize(0) { this->parent = _parent; // Inherited, can't set in the prefix. this->handlerFlags = kMPEG4_HandlerFlags; this->stdCharForm = kXMP_Char8Bit; + this->fileMode = (XMP_Uns8)_parent->tempUI32; + _parent->tempUI32 = 0; } // MPEG4_MetaHandler::MPEG4_MetaHandler @@ -212,675 +297,2137 @@ MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent ) : xmpBoxPos(0) MPEG4_MetaHandler::~MPEG4_MetaHandler() { - // Nothing to do yet. + // Nothing to do. } // MPEG4_MetaHandler::~MPEG4_MetaHandler // ================================================================================================= -// GetBoxInfo -// ========== -// -// Seek to the start of a box and extract the type and size, split the size into header and content -// portions. Leave the file positioned at the first byte of content. +// SecondsToXMPDate +// ================ -// ! We're returning the content size, not the raw (full) MPEG-4 box size! +// *** ASF has similar code with different origin, should make a shared utility. -static void GetBoxInfo ( LFA_FileRef fileRef, XMP_Uns64 fileSize, XMP_Uns64 boxPos, - XMP_Uns32 * boxType, XMP_Uns64 * headerSize, XMP_Uns64 * contentSize ) +static void SecondsToXMPDate ( XMP_Uns64 isoSeconds, XMP_DateTime * xmpDate ) { - XMP_Uns8 buffer [8]; - XMP_Uns32 u32Size; + memset ( xmpDate, 0, sizeof(XMP_DateTime) ); // AUDIT: Using sizeof(XMP_DateTime) is safe. - LFA_Seek ( fileRef, boxPos, SEEK_SET ); - (void) LFA_Read ( fileRef, buffer, 8, kLFA_RequireAll ); + XMP_Int32 days = (XMP_Int32) (isoSeconds / 86400); + isoSeconds -= ((XMP_Uns64)days * 86400); - u32Size = GetUns32BE ( &buffer[0] ); - *boxType = Get4CharCode ( &buffer[4] ); + XMP_Int32 hour = (XMP_Int32) (isoSeconds / 3600); + isoSeconds -= ((XMP_Uns64)hour * 3600); - if ( u32Size > 1 ) { - *headerSize = 8; // Normal explicit size case. - *contentSize = u32Size - 8; - } else if ( u32Size == 0 ) { - *headerSize = 8; // The box goes to EoF. - *contentSize = fileSize - (boxPos + 8); - } else { - XMP_Uns64 u64Size; // Have a 64-bit size. - (void) LFA_Read ( fileRef, &u64Size, 8, kLFA_RequireAll ); - u64Size = MakeUns64BE ( u64Size ); - *headerSize = 16; - *contentSize = u64Size - 16; + XMP_Int32 minute = (XMP_Int32) (isoSeconds / 60); + isoSeconds -= ((XMP_Uns64)minute * 60); + + XMP_Int32 second = (XMP_Int32)isoSeconds; + + xmpDate->year = 1904; // Start with the ISO origin. + xmpDate->month = 1; + xmpDate->day = 1; + + xmpDate->day += days; // Add in the delta. + xmpDate->hour = hour; + xmpDate->minute = minute; + xmpDate->second = second; + + xmpDate->hasTimeZone = true; // ! Needed for ConvertToUTCTime to do anything. + SXMPUtils::ConvertToUTCTime ( xmpDate ); // Normalize the date/time. + +} // SecondsToXMPDate + +// ================================================================================================= +// XMPDateToSeconds +// ================ + +// *** ASF has similar code with different origin, should make a shared utility. + +static bool IsLeapYear ( XMP_Int32 year ) +{ + if ( year < 0 ) year = -year + 1; // Fold the negative years, assuming there is a year 0. + if ( (year % 4) != 0 ) return false; // Not a multiple of 4. + if ( (year % 100) != 0 ) return true; // A multiple of 4 but not a multiple of 100. + if ( (year % 400) == 0 ) return true; // A multiple of 400. + return false; // A multiple of 100 but not a multiple of 400. +} + +// ------------------------------------------------------------------------------------------------- + +static XMP_Int32 DaysInMonth ( XMP_Int32 year, XMP_Int32 month ) +{ + static XMP_Int32 daysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + XMP_Int32 days = daysInMonth[month]; + if ( (month == 2) && IsLeapYear(year) ) days += 1; + return days; +} + +// ------------------------------------------------------------------------------------------------- + +static void XMPDateToSeconds ( const XMP_DateTime & _xmpDate, XMP_Uns64 * isoSeconds ) +{ + XMP_DateTime xmpDate = _xmpDate; + SXMPUtils::ConvertToUTCTime ( &xmpDate ); + + XMP_Uns64 tempSeconds = (XMP_Uns64)xmpDate.second; + tempSeconds += (XMP_Uns64)xmpDate.minute * 60; + tempSeconds += (XMP_Uns64)xmpDate.hour * 3600; + + XMP_Int32 days = (xmpDate.day - 1); + + --xmpDate.month; + while ( xmpDate.month >= 1 ) { + days += DaysInMonth ( xmpDate.year, xmpDate.month ); + --xmpDate.month; + } + + --xmpDate.year; + while ( xmpDate.year >= 1904 ) { + days += (IsLeapYear ( xmpDate.year) ? 366 : 365 ); + --xmpDate.year; } -} // GetBoxInfo + tempSeconds += (XMP_Uns64)days * 86400; + *isoSeconds = tempSeconds; + +} // XMPDateToSeconds // ================================================================================================= -// MPEG4_MetaHandler::CacheFileData -// ================================ -// -// The XMP in MPEG-4 is in a top level XMP 'uuid' box. Legacy metadata might be found in 2 places, -// the 'moov'/'mvhd' box, and the 'moov'/'udta'/'cprt' boxes. There is at most 1 each of the 'moov', -// 'mvhd' and 'udta' boxes. There are any number of 'cprt' boxes, including none. The 'udta' box can -// be large, so don't cache all of it. +// ImportMVHDItems +// =============== -void MPEG4_MetaHandler::CacheFileData() +static bool ImportMVHDItems ( MOOV_Manager::BoxInfo mvhdInfo, SXMPMeta * xmp ) { - XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + XMP_Assert ( mvhdInfo.boxType == ISOMedia::k_mvhd ); + if ( mvhdInfo.contentSize < 4 ) return false; // Just enough to check the version/flags at first. - LFA_FileRef fileRef = this->parent->fileRef; + XMP_Uns8 mvhdVersion = *mvhdInfo.content; + if ( mvhdVersion > 1 ) return false; + + XMP_Uns64 creationTime, modificationTime, duration; + XMP_Uns32 timescale; + + if ( mvhdVersion == 0 ) { + + if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return false; + MOOV_Manager::Content_mvhd_0 * mvhdRaw_0 = (MOOV_Manager::Content_mvhd_0*) mvhdInfo.content; - XMP_AbortProc abortProc = this->parent->abortProc; - void * abortArg = this->parent->abortArg; - const bool checkAbort = (abortProc != 0); + creationTime = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->creationTime ); + modificationTime = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->modificationTime ); + timescale = GetUns32BE ( &mvhdRaw_0->timescale ); + duration = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->duration ); - XMP_Uns64 fileSize = LFA_Measure ( fileRef ); - XMP_Uns64 outerPos, outerSize, hSize, cSize; - XMP_Uns32 boxType; + } else { - // The outer loop looks for the top level 'moov' and 'uuid'/XMP boxes. + XMP_Assert ( mvhdVersion == 1 ); + if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_1 ) ) return false; + MOOV_Manager::Content_mvhd_1 * mvhdRaw_1 = (MOOV_Manager::Content_mvhd_1*) mvhdInfo.content; + + creationTime = GetUns64BE ( &mvhdRaw_1->creationTime ); + modificationTime = GetUns64BE ( &mvhdRaw_1->modificationTime ); + timescale = GetUns32BE ( &mvhdRaw_1->timescale ); + duration = GetUns64BE ( &mvhdRaw_1->duration ); + + } + + bool haveImports = false; + XMP_DateTime xmpDate; + + if ( (creationTime >> 32) < 0xFF ) { // Sanity check for bogus date info. + SecondsToXMPDate ( creationTime, &xmpDate ); + xmp->SetProperty_Date ( kXMP_NS_XMP, "CreateDate", xmpDate ); + haveImports = true; + } + + if ( (modificationTime >> 32) < 0xFF ) { // Sanity check for bogus date info. + SecondsToXMPDate ( modificationTime, &xmpDate ); + xmp->SetProperty_Date ( kXMP_NS_XMP, "ModifyDate", xmpDate ); + haveImports = true; + } + + if ( timescale != 0 ) { // Avoid 1/0 for the scale field. + char buffer [32]; // A 64-bit number is at most 20 digits. + xmp->DeleteProperty ( kXMP_NS_DM, "duration" ); // Delete the whole struct. + snprintf ( buffer, sizeof(buffer), "%llu", duration ); // AUDIT: The buffer is big enough. + xmp->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", &buffer[0] ); + snprintf ( buffer, sizeof(buffer), "1/%u", timescale ); // AUDIT: The buffer is big enough. + xmp->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", &buffer[0] ); + haveImports = true; + } + + return haveImports; + +} // ImportMVHDItems + +// ================================================================================================= +// ExportMVHDItems +// =============== + +static void ExportMVHDItems ( const SXMPMeta & xmp, MOOV_Manager * moovMgr ) +{ + XMP_DateTime xmpDate; + bool createFound, modifyFound; + XMP_Uns64 createSeconds = 0, modifySeconds = 0; - bool moovFound = false; - if ( this->parent->openFlags & kXMPFiles_OpenOnlyXMP ) moovFound = true; // Ignore legacy. + MOOV_Manager::BoxInfo mvhdInfo; + MOOV_Manager::BoxRef mvhdRef = moovMgr->GetBox ( "moov/mvhd", &mvhdInfo ); + if ( (mvhdRef == 0) || (mvhdInfo.contentSize < 4) ) return; - for ( outerPos = 0; (outerPos < fileSize) && ((! this->containsXMP) || (! moovFound)); outerPos += outerSize ) { + XMP_Uns8 version = *mvhdInfo.content; + if ( version > 1 ) return; - if ( checkAbort && abortProc(abortArg) ) { - XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); + createFound = xmp.GetProperty_Date ( kXMP_NS_XMP, "CreateDate", &xmpDate, 0 ); + if ( createFound ) XMPDateToSeconds ( xmpDate, &createSeconds ); + + modifyFound = xmp.GetProperty_Date ( kXMP_NS_XMP, "ModifyDate", &xmpDate, 0 ); + if ( modifyFound ) XMPDateToSeconds ( xmpDate, &modifySeconds ); + + if ( version == 1 ) { + + // Modify the v1 box in-place. + + if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_1 ) ) return; + + XMP_Uns64 oldCreate = GetUns64BE ( mvhdInfo.content + 4 ); + XMP_Uns64 oldModify = GetUns64BE ( mvhdInfo.content + 12 ); + + if ( createFound ) { + if ( createSeconds != oldCreate ) PutUns64BE ( createSeconds, ((XMP_Uns8*)mvhdInfo.content + 4) ); + moovMgr->NoteChange(); + } + if ( modifyFound ) { + if ( modifySeconds != oldModify ) PutUns64BE ( modifySeconds, ((XMP_Uns8*)mvhdInfo.content + 12) ); + moovMgr->NoteChange(); } + + } else if ( ((createSeconds >> 32) == 0) && ((modifySeconds >> 32) == 0) ) { - GetBoxInfo ( fileRef, fileSize, outerPos, &boxType, &hSize, &cSize ); - outerSize = hSize + cSize; + // Modify the v0 box in-place. + + if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return; + + XMP_Uns32 oldCreate = GetUns32BE ( mvhdInfo.content + 4 ); + XMP_Uns32 oldModify = GetUns32BE ( mvhdInfo.content + 8 ); - if ( (! this->containsXMP) && (boxType == kBE_uuid) ) { + if ( createFound ) { + if ( (XMP_Uns32)createSeconds != oldCreate ) PutUns32BE ( (XMP_Uns32)createSeconds, ((XMP_Uns8*)mvhdInfo.content + 4) ); + moovMgr->NoteChange(); + } + if ( modifyFound ) { + if ( (XMP_Uns32)modifySeconds != oldModify ) PutUns32BE ( (XMP_Uns32)modifySeconds, ((XMP_Uns8*)mvhdInfo.content + 8) ); + moovMgr->NoteChange(); + } + + } else { - XMP_Uns8 uuid [16]; - LFA_Read ( fileRef, uuid, 16, kLFA_RequireAll ); - - if ( memcmp ( uuid, kBE_xmpUUID, 16 ) == 0 ) { + // Replace the v0 box with a v1 box. + + XMP_Assert ( createFound | modifyFound ); // One of them has high bits set. + if ( mvhdInfo.contentSize != sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return; + + MOOV_Manager::Content_mvhd_0 * mvhdV0 = (MOOV_Manager::Content_mvhd_0*) mvhdInfo.content; + MOOV_Manager::Content_mvhd_1 mvhdV1; + + // Copy the unchanged fields directly. + + mvhdV1.timescale = mvhdV0->timescale; + mvhdV1.rate = mvhdV0->rate; + mvhdV1.volume = mvhdV0->volume; + mvhdV1.pad_1 = mvhdV0->pad_1; + mvhdV1.pad_2 = mvhdV0->pad_2; + mvhdV1.pad_3 = mvhdV0->pad_3; + for ( int i = 0; i < 9; ++i ) mvhdV1.matrix[i] = mvhdV0->matrix[i]; + for ( int i = 0; i < 6; ++i ) mvhdV1.preDef[i] = mvhdV0->preDef[i]; + mvhdV1.nextTrackID = mvhdV0->nextTrackID; + + // Set the fields that have changes. + + mvhdV1.vFlags = (1 << 24) | (mvhdV0->vFlags & 0xFFFFFF); + mvhdV1.duration = MakeUns64BE ( (XMP_Uns64) GetUns32BE ( &mvhdV0->duration ) ); + + XMP_Uns64 temp64; + + temp64 = (XMP_Uns64) GetUns32BE ( &mvhdV0->creationTime ); + if ( createFound ) temp64 = createSeconds; + mvhdV1.creationTime = MakeUns64BE ( temp64 ); + + temp64 = (XMP_Uns64) GetUns32BE ( &mvhdV0->modificationTime ); + if ( modifyFound ) temp64 = modifySeconds; + mvhdV1.modificationTime = MakeUns64BE ( temp64 ); + + moovMgr->SetBox ( mvhdRef, &mvhdV1, sizeof ( MOOV_Manager::Content_mvhd_1 ) ); + + } + +} // ExportMVHDItems + +// ================================================================================================= +// ImportISOCopyrights +// =================== +// +// The cached 'moov'/'udta'/'cprt' boxes are full boxes. The "real" content is a UInt16 packed 3 +// character language code and a UTF-8 or UTF-16 string. + +static bool ImportISOCopyrights ( const std::vector<MOOV_Manager::BoxInfo> & cprtBoxes, SXMPMeta * xmp ) +{ + bool haveImports = false; + + std::string tempStr; + char lang3 [4]; // The unpacked ISO-639-2/T language code with final null. + lang3[3] = 0; + + for ( size_t i = 0, limit = cprtBoxes.size(); i < limit; ++i ) { + + const MOOV_Manager::BoxInfo & currBox = cprtBoxes[i]; + if ( currBox.contentSize < 4+2+1 ) continue; // Want enough for a non-empty value. + if ( *currBox.content != 0 ) continue; // Only proceed for version 0, ignore the flags. + + XMP_Uns16 packedLang = GetUns16BE ( currBox.content + 4 ); + lang3[0] = (packedLang >> 10) + 0x60; + lang3[1] = ((packedLang >> 5) & 0x1F) + 0x60; + lang3[2] = (packedLang & 0x1F) + 0x60; + XMP_StringPtr xmpLang = Lookup2LetterLang ( lang3 ); + if ( *xmpLang == 0 ) continue; + + XMP_StringPtr textPtr = (XMP_StringPtr) (currBox.content + 6); + XMP_StringLen textLen = currBox.contentSize - 6; + + if ( (textLen >= 2) && (GetUns16BE(textPtr) == 0xFEFF) ) { + FromUTF16 ( (UTF16Unit*)textPtr, textLen/2, &tempStr, true /* big endian */ ); + textPtr = tempStr.c_str(); + } + + xmp->SetLocalizedText ( kXMP_NS_DC, "rights", xmpLang, xmpLang, textPtr ); + haveImports = true; + + } + + return haveImports; + +} // ImportISOCopyrights + +// ================================================================================================= +// ExportISOCopyrights +// =================== + +static void ExportISOCopyrights ( const SXMPMeta & xmp, MOOV_Manager * moovMgr ) +{ + bool haveMappings = false; // True if any ISO-XMP language mappings are found. + + // Go through the ISO 'cprt' items and look for a corresponding XMP item. Ignore the ISO item if + // there is no language mapping to XMP. Update the ISO item if the mappable XMP exists, delete + // the ISO item if the mappable XMP does not exist. Since the import side would have made sure + // the mappable XMP items existed, if they don't now they must have been deleted. + + MOOV_Manager::BoxInfo udtaInfo; + MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo ); + if ( udtaRef == 0 ) return; + + std::string xmpPath, xmpValue, xmpLang, tempStr; + char lang3 [4]; // An unpacked ISO-639-2/T language code. + lang3[3] = 0; + + for ( XMP_Uns32 ordinal = udtaInfo.childCount; ordinal > 0; --ordinal ) { // ! Go backwards because of deletions. + + MOOV_Manager::BoxInfo cprtInfo; + MOOV_Manager::BoxRef cprtRef = moovMgr->GetNthChild ( udtaRef, ordinal-1, &cprtInfo ); + if ( (cprtRef == 0) ) break; // Sanity check, should not happen. + if ( (cprtInfo.boxType != ISOMedia::k_cprt) || (cprtInfo.contentSize < 6) ) continue; + if ( *cprtInfo.content != 0 ) continue; // Only accept version 0, ignore the flags. + + XMP_Uns16 packedLang = GetUns16BE ( cprtInfo.content + 4 ); + lang3[0] = (packedLang >> 10) + 0x60; + lang3[1] = ((packedLang >> 5) & 0x1F) + 0x60; + lang3[2] = (packedLang & 0x1F) + 0x60; + + XMP_StringPtr lang2 = Lookup2LetterLang ( lang3 ); + if ( *lang2 == 0 ) continue; // No language mapping to XMP. + haveMappings = true; + + bool xmpFound = xmp.GetLocalizedText ( kXMP_NS_DC, "rights", lang2, lang2, &xmpLang, &xmpValue, 0 ); + if ( xmpFound ) { + if ( (xmpLang.size() < 2) || + ( (xmpLang.size() == 2) && (xmpLang != lang2) ) || + ( (xmpLang.size() > 2) && ( (xmpLang[2] != '-') || (! XMP_LitNMatch ( xmpLang.c_str(), lang2, 2)) ) ) ) { + xmpFound = false; // The language does not match, the corresponding XMP does not exist. + } + } - // Found the XMP, record the offset and size, read the packet. + if ( ! xmpFound ) { - this->containsXMP = true; - this->xmpBoxPos = outerPos; - this->packetInfo.offset = outerPos + hSize + 16; - this->packetInfo.length = (XMP_Int32) (cSize - 16); + // No XMP, delete the ISO item. + moovMgr->DeleteNthChild ( udtaRef, ordinal-1 ); - this->xmpPacket.reserve ( this->packetInfo.length ); - this->xmpPacket.assign ( this->packetInfo.length, ' ' ); - LFA_Read ( fileRef, (void*)this->xmpPacket.data(), this->packetInfo.length, kLFA_RequireAll ); + } else { + // Update the ISO item if necessary. + XMP_StringPtr isoStr = (XMP_StringPtr)cprtInfo.content + 6; + size_t rawLen = cprtInfo.contentSize - 6; + if ( (rawLen >= 8) && (GetUns16BE(isoStr) == 0xFEFF) ) { + FromUTF16 ( (UTF16Unit*)(isoStr+2), (rawLen-2)/2, &tempStr, true /* big endian */ ); + isoStr = tempStr.c_str(); + } + if ( xmpValue != isoStr ) { + std::string newContent = "vfffll"; + newContent += xmpValue; + memcpy ( (char*)newContent.c_str(), cprtInfo.content, 6 ); // Keep old version, flags, and language. + moovMgr->SetBox ( cprtRef, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) ); } - } else if ( (! moovFound) && (boxType == kBE_moov) ) { + } - // The middle loop loop looks for the 'moov'/'mvhd' and 'moov'/'udta' boxes. + } - moovFound = true; - - XMP_Uns64 middleStart = outerPos + hSize; - XMP_Uns64 middleEnd = outerPos + outerSize; - XMP_Uns64 middlePos, middleSize; + // Go through the XMP items and look for a corresponding ISO item. Skip if found (did it above), + // otherwise add a new ISO item. - bool mvhdFound = false, udtaFound = false; + bool haveXDefault = false; + XMP_Index xmpCount = xmp.CountArrayItems ( kXMP_NS_DC, "rights" ); - for ( middlePos = middleStart; (middlePos < middleEnd) && ((! mvhdFound) || (! udtaFound)); middlePos += middleSize ) { + for ( XMP_Index xmpIndex = 1; xmpIndex <= xmpCount; ++xmpIndex ) { // ! The first XMP array index is 1. - if ( checkAbort && abortProc(abortArg) ) { - XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); - } + SXMPUtils::ComposeArrayItemPath ( kXMP_NS_DC, "rights", xmpIndex, &xmpPath ); + xmp.GetArrayItem ( kXMP_NS_DC, "rights", xmpIndex, &xmpValue, 0 ); + bool hasLang = xmp.GetQualifier ( kXMP_NS_DC, xmpPath.c_str(), kXMP_NS_XML, "lang", &xmpLang, 0 ); + if ( ! hasLang ) continue; // Sanity check. + if ( xmpLang == "x-default" ) { + haveXDefault = true; // See later special case. + continue; + } - GetBoxInfo ( fileRef, fileSize, middlePos, &boxType, &hSize, &cSize ); - middleSize = hSize + cSize; + XMP_StringPtr isoLang = ""; + size_t rootLen = xmpLang.find ( '-' ); + if ( rootLen == std::string::npos ) rootLen = xmpLang.size(); + if ( rootLen == 2 ) { + xmpLang[2] = 0; + isoLang = Lookup3LetterLang ( xmpLang.c_str() ); + if ( *isoLang == 0 ) continue; + } else if ( rootLen == 3 ) { + xmpLang[3] = 0; + isoLang = xmpLang.c_str(); + } else { + continue; + } + haveMappings = true; - if ( (! mvhdFound) && (boxType == kBE_mvhd) ) { + bool isoFound = false; + XMP_Uns16 packedLang = ((isoLang[0] - 0x60) << 10) | ((isoLang[1] - 0x60) << 5) | (isoLang[2] - 0x60); - // Save the entire 'moov'/'mvhd' box content, it isn't very big. - mvhdFound = true; - this->mvhdBox.reserve ( (size_t)cSize ); - this->mvhdBox.assign ( (size_t)cSize, ' ' ); - LFA_Read ( fileRef, (void*)this->mvhdBox.data(), (XMP_Int32)cSize, kLFA_RequireAll ); + for ( XMP_Uns32 isoIndex = 0; (isoIndex < udtaInfo.childCount) && (! isoFound); ++isoIndex ) { - } else if ( (! udtaFound) && (boxType == kBE_udta) ) { + MOOV_Manager::BoxInfo cprtInfo; + MOOV_Manager::BoxRef cprtRef = moovMgr->GetNthChild ( udtaRef, isoIndex, &cprtInfo ); + if ( (cprtRef == 0) ) break; // Sanity check, should not happen. + if ( (cprtInfo.boxType != ISOMedia::k_cprt) || (cprtInfo.contentSize < 6) ) continue; + if ( *cprtInfo.content != 0 ) continue; // Only accept version 0, ignore the flags. + if ( packedLang != GetUns16BE ( cprtInfo.content + 4 ) ) continue; // Look for matching language. - // The inner loop looks for the 'moov'/'udta'/'cprt' boxes. - - udtaFound = true; - XMP_Uns64 innerStart = middlePos + hSize; - XMP_Uns64 innerEnd = middlePos + middleSize; - XMP_Uns64 innerPos, innerSize; + isoFound = true; // Found the language entry, whether or not we update it. - for ( innerPos = innerStart; innerPos < innerEnd; innerPos += innerSize ) { - - if ( checkAbort && abortProc(abortArg) ) { - XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); - } + } + + if ( ! isoFound ) { + + std::string newContent = "vfffll"; + newContent += xmpValue; + *((XMP_Uns32*)newContent.c_str()) = 0; // Set the version and flags to zero. + PutUns16BE ( packedLang, (char*)newContent.c_str() + 4 ); + moovMgr->AddChildBox ( udtaRef, ISOMedia::k_cprt, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) ); + + } + + } + + // If there were no mappings in the loops, export the XMP "x-default" value to the first ISO item. + + if ( ! haveMappings ) { + + MOOV_Manager::BoxInfo cprtInfo; + MOOV_Manager::BoxRef cprtRef = moovMgr->GetTypeChild ( udtaRef, ISOMedia::k_cprt, &cprtInfo ); - GetBoxInfo ( fileRef, fileSize, innerPos, &boxType, &hSize, &cSize ); - innerSize = hSize + cSize; - if ( boxType != kBE_cprt ) continue; + if ( (cprtRef != 0) && (cprtInfo.contentSize >= 6) && (*cprtInfo.content == 0) ) { - // ! Actually capturing structured data - the 'cprt' box content. - this->cprtBoxes.push_back ( std::string() ); - std::string & newCprt = this->cprtBoxes.back(); - newCprt.reserve ( (size_t)cSize ); - newCprt.assign ( (size_t)cSize, ' ' ); - LFA_Read ( fileRef, (void*)newCprt.data(), (XMP_Int32)cSize, kLFA_RequireAll ); + bool xmpFound = xmp.GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", &xmpLang, &xmpValue, 0 ); + + if ( xmpFound && (xmpLang == "x-default") ) { + + // Update the ISO item if necessary. + XMP_StringPtr isoStr = (XMP_StringPtr)cprtInfo.content + 6; + size_t rawLen = cprtInfo.contentSize - 6; + if ( (rawLen >= 8) && (GetUns16BE(isoStr) == 0xFEFF) ) { + FromUTF16 ( (UTF16Unit*)(isoStr+2), (rawLen-2)/2, &tempStr, true /* big endian */ ); + isoStr = tempStr.c_str(); + } + if ( xmpValue != isoStr ) { + std::string newContent = "vfffll"; + newContent += xmpValue; + memcpy ( (char*)newContent.c_str(), cprtInfo.content, 6 ); // Keep old version, flags, and language. + moovMgr->SetBox ( cprtRef, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) ); + } + + } + + } - } // inner loop + } - } // 'moov'/'udta' box +} // ExportISOCopyrights - } // middle loop +// ================================================================================================= +// ExportQuickTimeItems +// ==================== - } // 'moov' box +static void ExportQuickTimeItems ( const SXMPMeta & xmp, TradQT_Manager * qtMgr, MOOV_Manager * moovMgr ) +{ - } // outer loop + // The QuickTime 'udta' timecode items are done here for simplicity. + + #define createWithZeroLang true -} // MPEG4_MetaHandler::CacheFileData + qtMgr->ExportSimpleXMP ( kQTilst_Reel, xmp, kXMP_NS_DM, "tapeName" ); + qtMgr->ExportSimpleXMP ( kQTilst_Timecode, xmp, kXMP_NS_DM, "startTimecode/xmpDM:timeValue", createWithZeroLang ); + qtMgr->ExportSimpleXMP ( kQTilst_TimeScale, xmp, kXMP_NS_DM, "startTimeScale", createWithZeroLang ); + qtMgr->ExportSimpleXMP ( kQTilst_TimeSize, xmp, kXMP_NS_DM, "startTimeSampleSize", createWithZeroLang ); + + qtMgr->UpdateChangedBoxes ( moovMgr ); + +} // ExportQuickTimeItems // ================================================================================================= -// MPEG4_MetaHandler::MakeLegacyDigest -// =================================== +// SelectTimeFormat +// ================ -// *** Will need updating if we process the 'ilst' metadata. +static const char * SelectTimeFormat ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo ) +{ + const char * timeFormat = 0; + + float fltFPS = (float)tmcdInfo.timeScale / (float)tmcdInfo.frameDuration; + int intFPS = (int) (fltFPS + 0.5); -#define kHexDigits "0123456789ABCDEF" + switch ( intFPS ) { -void MPEG4_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) + case 30: + if ( fltFPS >= 29.985 ) { + timeFormat = "30Timecode"; + } else if ( tmcdInfo.isDropFrame ) { + timeFormat = "2997DropTimecode"; + } else { + timeFormat = "2997NonDropTimecode"; + } + break; + + case 24: + if ( fltFPS >= 23.988 ) { + timeFormat = "24Timecode"; + } else { + timeFormat = "23976Timecode"; + } + break; + + case 25: + timeFormat = "25Timecode"; + break; + + case 50: + timeFormat = "50Timecode"; + break; + + case 60: + if ( fltFPS >= 59.97 ) { + timeFormat = "60Timecode"; + } else if ( tmcdInfo.isDropFrame ) { + timeFormat = "5994DropTimecode"; + } else { + timeFormat = "5994NonDropTimecode"; + } + break; + + } + + return timeFormat; + +} // SelectTimeFormat + +// ================================================================================================= +// SelectTimeFormat +// ================ + +static const char * SelectTimeFormat ( const SXMPMeta & xmp ) { - MD5_CTX context; - unsigned char digestBin [16]; - MD5Init ( &context ); + bool ok; + MPEG4_MetaHandler::TimecodeTrackInfo tmcdInfo; + + XMP_Int64 timeScale; + ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", &timeScale, 0 ); + if ( ! ok ) return 0; + tmcdInfo.timeScale = (XMP_Uns32)timeScale; + + XMP_Int64 frameDuration; + ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", &frameDuration, 0 ); + if ( ! ok ) return 0; + tmcdInfo.frameDuration = (XMP_Uns32)frameDuration; + + std::string timecode; + ok = xmp.GetProperty ( kXMP_NS_DM, "startTimecode/xmpDM:timeValue", &timecode, 0 ); + if ( ! ok ) return 0; + if ( (timecode.size() == 11) && (timecode[8] == ';') ) tmcdInfo.isDropFrame = true; + + return SelectTimeFormat ( tmcdInfo ); + +} // SelectTimeFormat + +// ================================================================================================= +// ComposeTimecode +// =============== + +static const char * kDecDigits = "0123456789"; +#define TwoDigits(val,str) (str)[0] = kDecDigits[(val)/10]; (str)[1] = kDecDigits[(val)%10] + +static bool ComposeTimecode ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo, std::string * strValue ) +{ + float fltFPS = (float)tmcdInfo.timeScale / (float)tmcdInfo.frameDuration; + int intFPS = (int) (fltFPS + 0.5); + if ( (intFPS != 30) && (intFPS != 24) && (intFPS != 25) && (intFPS != 50) && (intFPS != 60) ) return false; + + XMP_Uns32 framesPerDay = intFPS * 24*60*60; + XMP_Uns32 dropLimit = 2; // Used in the drop-frame correction. + + if ( tmcdInfo.isDropFrame ) { + if ( intFPS == 30 ) { + framesPerDay = 2589408; // = 29.97 * 24*60*60 + } else if ( intFPS == 60 ) { + framesPerDay = 5178816; // = 59.94 * 24*60*60 + dropLimit = 4; + } else { + strValue->erase(); + return false; // Dropframe can only apply to 29.97 and 59.94. + } + } + + XMP_Uns32 framesPerHour = framesPerDay / 24; + XMP_Uns32 framesPerTenMinutes = framesPerHour / 6; + XMP_Uns32 framesPerMinute = framesPerTenMinutes / 10; + + XMP_Uns32 frameCount = tmcdInfo.timecodeSample; + while (frameCount >= framesPerDay ) frameCount -= framesPerDay; // Normalize to be within 24 hours. + + XMP_Uns32 hours, minHigh, minLow, seconds; + + hours = frameCount / framesPerHour; + frameCount -= (hours * framesPerHour); + + minHigh = frameCount / framesPerTenMinutes; + frameCount -= (minHigh * framesPerTenMinutes); + + minLow = frameCount / framesPerMinute; + frameCount -= (minLow * framesPerMinute); + + // Do some drop-frame corrections at this point: If this is drop-frame and the units of minutes + // is non-zero, and the seconds are zero, and the frames are zero or one, the time is illegal. + // Perform correction by subtracting 1 from the units of minutes and adding 1798 to the frames.Ê + // For example, 1:00:00 becomes 59:28, and 1:00:01 becomes 59:29. A special case can occur for + // when the frameCount just before the minHigh calculation is less than framesPerTenMinutes but + // more than 10*framesPerMinute. This happens because of roundoff, and will result in a minHigh + // of 0 and a minLow of 10.ÊThe drop frame correction mustÊalso be performed for this case. + + if ( tmcdInfo.isDropFrame ) { + if ( (minLow == 10) || ((minLow != 0) && (frameCount < dropLimit)) ) { + minLow -= 1; + frameCount += framesPerMinute; + } + } + + seconds = frameCount / intFPS; + frameCount -= (seconds * intFPS); + + if ( tmcdInfo.isDropFrame ) { + *strValue = "hh;mm;ss;ff"; + } else { + *strValue = "hh:mm:ss:ff"; + } + + char * str = (char*)strValue->c_str(); + TwoDigits ( hours, str ); + str[3] = kDecDigits[minHigh]; str[4] = kDecDigits[minLow]; + TwoDigits ( seconds, str+6 ); + TwoDigits ( frameCount, str+9 ); + + return true; + +} // ComposeTimecode + +// ================================================================================================= +// DecomposeTimecode +// ================= + +static bool DecomposeTimecode ( const char * strValue, MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo ) +{ + float fltFPS = (float)tmcdInfo->timeScale / (float)tmcdInfo->frameDuration; + int intFPS = (int) (fltFPS + 0.5); + if ( (intFPS != 30) && (intFPS != 24) && (intFPS != 25) && (intFPS != 50) && (intFPS != 60) ) return false; + + XMP_Uns32 framesPerDay = intFPS * 24*60*60; + + int items, hours, minutes, seconds, frames; + + if ( ! tmcdInfo->isDropFrame ) { + items = sscanf ( strValue, "%d:%d:%d:%d", &hours, &minutes, &seconds, &frames ); + } else { + items = sscanf ( strValue, "%d;%d;%d;%d", &hours, &minutes, &seconds, &frames ); + if ( intFPS == 30 ) { + framesPerDay = 2589408; // = 29.97 * 24*60*60 + } else if ( intFPS == 60 ) { + framesPerDay = 5178816; // = 59.94 * 24*60*60 + } else { + return false; // Dropframe can only apply to 29.97 and 59.94. + } + } - MD5Update ( &context, (XMP_Uns8*)this->mvhdBox.c_str(), (unsigned int) this->mvhdBox.size() ); + if ( items != 4 ) return false; + int minHigh = minutes / 10; + int minLow = minutes % 10; + + XMP_Uns32 framesPerHour = framesPerDay / 24; + XMP_Uns32 framesPerTenMinutes = framesPerHour / 6; + XMP_Uns32 framesPerMinute = framesPerTenMinutes / 10; + + tmcdInfo->timecodeSample = (hours * framesPerHour) + (minHigh * framesPerTenMinutes) + + (minLow * framesPerMinute) + (seconds * intFPS) + frames; + + return true; + +} // DecomposeTimecode + +// ================================================================================================= +// FindTimecodeTrack +// ================= +// +// Look for a well-formed timecode track, return the .../mdia/minf/stbl box ref. + +static MOOV_Manager::BoxRef FindTimecodeTrack ( const MOOV_Manager & moovMgr ) +{ + + // Find a 'trak' box with a handler type of 'tmcd'. - for ( size_t i = 0, limit = this->cprtBoxes.size(); i < limit; ++i ) { - const std::string & currCprt = this->cprtBoxes[i]; - MD5Update ( &context, (XMP_Uns8*)currCprt.c_str(), (unsigned int) currCprt.size() ); + MOOV_Manager::BoxInfo moovInfo; + MOOV_Manager::BoxRef moovRef = moovMgr.GetBox ( "moov", &moovInfo ); + XMP_Assert ( moovRef != 0 ); + + MOOV_Manager::BoxInfo trakInfo; + MOOV_Manager::BoxRef trakRef; + + size_t i = 0; + for ( ; i < moovInfo.childCount; ++i ) { + + trakRef = moovMgr.GetNthChild ( moovRef, i, &trakInfo ); + if ( trakRef == 0 ) return 0; // Sanity check, should not happen. + if ( trakInfo.boxType != ISOMedia::k_trak ) continue; + + MOOV_Manager::BoxRef innerRef; + MOOV_Manager::BoxInfo innerInfo; + + innerRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &innerInfo ); + if ( innerRef == 0 ) continue; + + innerRef = moovMgr.GetTypeChild ( innerRef, ISOMedia::k_hdlr, &innerInfo ); + if ( (innerRef == 0) || (innerInfo.contentSize < sizeof ( MOOV_Manager::Content_hdlr )) ) continue; + + const MOOV_Manager::Content_hdlr * hdlr = (MOOV_Manager::Content_hdlr*) innerInfo.content; + if ( hdlr->versionFlags != 0 ) continue; + if ( GetUns32BE ( &hdlr->handlerType ) == ISOMedia::k_tmcd ) break; + + } + if ( i == moovInfo.childCount ) return 0; + + // Find the .../mdia/minf/stbl box. + + MOOV_Manager::BoxInfo tempInfo; + MOOV_Manager::BoxRef tempRef, stblRef; + + tempRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &tempInfo ); + if ( tempRef == 0 ) return 0; + + tempRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, &tempInfo ); + if ( tempRef == 0 ) return 0; + + stblRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stbl, &tempInfo ); + return stblRef; + +} // FindTimecodeTrack + +// ================================================================================================= +// ImportTimecodeItems +// =================== + +static bool ImportTimecodeItems ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo, + const TradQT_Manager & qtInfo, SXMPMeta * xmp ) +{ + std::string xmpValue; + bool haveItem; + bool haveImports = false; + + // The QT user data item '©REL' goes into xmpDM:tapeName, and the 'name' box at the end of the + // timecode sample description goes into xmpDM:altTapeName. + haveImports |= qtInfo.ImportSimpleXMP ( kQTilst_Reel, xmp, kXMP_NS_DM, "tapeName" ); + if ( ! tmcdInfo.macName.empty() ) { + haveItem = ConvertFromMacLang ( tmcdInfo.macName, tmcdInfo.macLang, &xmpValue ); + if ( haveItem ) { + xmp->SetProperty ( kXMP_NS_DM, "altTapeName", xmpValue.c_str() ); + haveImports = true; + } + } + + // The QT user data item '©TSC' goes into xmpDM:startTimeScale. If that isn't present, then + // the timecode sample description's timeScale is used. + haveItem = qtInfo.ImportSimpleXMP ( kQTilst_TimeScale, xmp, kXMP_NS_DM, "startTimeScale" ); + if ( tmcdInfo.stsdBoxFound & (! haveItem) ) { + xmp->SetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", tmcdInfo.timeScale ); + haveItem = true; + } + haveImports |= haveItem; + + // The QT user data item '©TSZ' goes into xmpDM:startTimeSampleSize. If that isn't present, then + // the timecode sample description's frameDuration is used. + haveItem = qtInfo.ImportSimpleXMP ( kQTilst_TimeSize, xmp, kXMP_NS_DM, "startTimeSampleSize" ); + if ( tmcdInfo.stsdBoxFound & (! haveItem) ) { + xmp->SetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", tmcdInfo.frameDuration ); + haveItem = true; } + haveImports |= haveItem; - MD5Final ( digestBin, &context ); + const char * timeFormat; + + // The Timecode struct type is used for xmpDM:startTimecode and xmpDM:altTimecode. For both, only + // the xmpDM:timeValue and xmpDM:timeFormat fields are set. + + // The QT user data item '©TIM' goes into xmpDM:startTimecode/xmpDM:timeValue. This is an already + // formatted timecode string. The XMP values of xmpDM:startTimeScale, xmpDM:startTimeSampleSize, + // and xmpDM:startTimecode/xmpDM:timeValue are used to select the timeFormat value. + haveImports |= qtInfo.ImportSimpleXMP ( kQTilst_Timecode, xmp, kXMP_NS_DM, "startTimecode/xmpDM:timeValue" ); + timeFormat = SelectTimeFormat ( *xmp ); + if ( timeFormat != 0 ) { + xmp->SetProperty ( kXMP_NS_DM, "startTimecode/xmpDM:timeFormat", timeFormat ); + haveImports = true; + } + + if ( tmcdInfo.stsdBoxFound ) { + + haveItem = ComposeTimecode ( tmcdInfo, &xmpValue ); + if ( haveItem ) { + xmp->SetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeValue", xmpValue.c_str() ); + haveImports = true; + } + + timeFormat = SelectTimeFormat ( tmcdInfo ); + if ( timeFormat != 0 ) { + xmp->SetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeFormat", timeFormat ); + haveImports = true; + } - char buffer [40]; - for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { - XMP_Uns8 byte = digestBin[in]; - buffer[out] = kHexDigits [ byte >> 4 ]; - buffer[out+1] = kHexDigits [ byte & 0xF ]; } - buffer[32] = 0; - digestStr->erase(); - digestStr->append ( buffer, 32 ); -} // MPEG4_MetaHandler::MakeLegacyDigest + return haveImports; + +} // ImportTimecodeItems // ================================================================================================= +// ExportTimecodeItems +// =================== + +static void ExportTimecodeItems ( const SXMPMeta & xmp, MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo, + TradQT_Manager * qtMgr, MOOV_Manager * moovMgr ) +{ + // Export the items that go into the timecode track: + // - the timescale and frame duration in the first 'stsd' table entry + // - the 'name' box appended to the first 'stsd' table entry + // - the first timecode sample + // ! The QuickTime 'udta' timecode items are handled in ExportQuickTimeItems. + + if ( ! tmcdInfo->stsdBoxFound ) return; // Don't make changes unless there is a well-formed timecode track. + + MOOV_Manager::BoxRef stblRef = FindTimecodeTrack ( *moovMgr ); + if ( stblRef == 0 ) return; + + MOOV_Manager::BoxInfo stsdInfo; + MOOV_Manager::BoxRef stsdRef; + + stsdRef = moovMgr->GetTypeChild ( stblRef, ISOMedia::k_stsd, &stsdInfo ); + if ( stsdRef == 0 ) return; + if ( stsdInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsd_entry )) ) return; + if ( GetUns32BE ( stsdInfo.content + 4 ) == 0 ) return; // Make sure the entry count is non-zero. + + const MOOV_Manager::Content_stsd_entry * stsdRawEntry = (MOOV_Manager::Content_stsd_entry*) (stsdInfo.content + 8); + + XMP_Uns32 stsdEntrySize = GetUns32BE ( &stsdRawEntry->entrySize ); + if ( stsdEntrySize > (stsdInfo.contentSize - 4) ) stsdEntrySize = stsdInfo.contentSize - 4; + if ( stsdEntrySize < sizeof ( MOOV_Manager::Content_stsd_entry ) ) return; + + bool ok; + std::string xmpValue; + XMP_Int64 int64; // Used to allow UInt32 values, GetProperty_Int is SInt32. + + // The tmcdInfo timeScale field is set from xmpDM:startTimeScale. + ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", &int64, 0 ); + if ( ok && (int64 <= 0xFFFFFFFF) && ((XMP_Uns32)int64 != tmcdInfo->timeScale) ) { + tmcdInfo->timeScale = (XMP_Uns32)int64; + PutUns32BE ( tmcdInfo->timeScale, (void*)&stsdRawEntry->timeScale ); + moovMgr->NoteChange(); + } + + // The tmcdInfo frameDuration field is set from xmpDM:startTimeSampleSize. + ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", &int64, 0 ); + if ( ok && (int64 <= 0xFFFFFFFF) && ((XMP_Uns32)int64 != tmcdInfo->frameDuration) ) { + tmcdInfo->frameDuration = (XMP_Uns32)int64; + PutUns32BE ( tmcdInfo->frameDuration, (void*)&stsdRawEntry->frameDuration ); + moovMgr->NoteChange(); + } + + // The tmcdInfo isDropFrame flag is set from xmpDM:altTimecode/xmpDM:timeValue. The timeScale + // and frameDuration must be updated first, they are used by DecomposeTimecode. Compute the new + // UInt32 timecode sample, but it gets written to the file later by UpdateFile. + + ok = xmp.GetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeValue", &xmpValue, 0 ); + if ( ok && (xmpValue.size() == 11) ) { + + bool oldDropFrame = tmcdInfo->isDropFrame; + tmcdInfo->isDropFrame = false; + if ( xmpValue[8] == ';' ) tmcdInfo->isDropFrame = true; + if ( oldDropFrame != tmcdInfo->isDropFrame ) { + XMP_Uns32 flags = GetUns32BE ( &stsdRawEntry->flags ); + flags = (flags & 0xFFFFFFFE) | (XMP_Uns32)tmcdInfo->isDropFrame; + PutUns32BE ( flags, (void*)&stsdRawEntry->flags ); + moovMgr->NoteChange(); + } + + XMP_Uns32 oldSample = tmcdInfo->timecodeSample; + ok = DecomposeTimecode ( xmpValue.c_str(), tmcdInfo ); + if ( ok && (oldSample != tmcdInfo->timecodeSample) ) moovMgr->NoteChange(); -struct MVHD_v1 { // v1 v0 - offsets within the content portion of the 'mvhd' box - XMP_Uns8 version; // 0 0 - XMP_Uns8 flags [3]; // 1 1 - XMP_Uns64 creationTime; // 4 4 - Uns32 in v0 - XMP_Uns64 modificationTime; // 12 8 - Uns32 in v0 - XMP_Uns32 timescale; // 20 12 - XMP_Uns64 duration; // 24 16 - Uns32 in v0 - XMP_Int32 rate; // 32 20 - XMP_Int16 volume; // 36 24 - XMP_Uns16 pad_1; // 38 26 - XMP_Uns32 pad_2, pad_3; // 40 28 - XMP_Int32 matrix [9]; // 48 36 - XMP_Uns32 preDef [6]; // 84 72 - XMP_Uns32 nextTrackID; // 108 96 -}; // 112 100 + } + + // The 'name' box attached to the first 'stsd' table entry is set from xmpDM:altTapeName. + + bool replaceNameBox = false; + + ok = xmp.GetProperty ( kXMP_NS_DM, "altTapeName", &xmpValue, 0 ); + if ( (! ok) || xmpValue.empty() ) { + if ( tmcdInfo->nameOffset != 0 ) replaceNameBox = true; // No XMP, get rid of existing name. + } else { + std::string macValue; + ok = ConvertToMacLang ( xmpValue, tmcdInfo->macLang, &macValue ); + if ( ok && (macValue != tmcdInfo->macName) ) { + tmcdInfo->macName = macValue; + replaceNameBox = true; // Write changed name. + } + } + + if ( replaceNameBox ) { + + // To replace the 'name' box we have to create an entire new 'stsd' box, and attach the + // new name to the first 'stsd' table entry. The 'name' box content is a UInt16 text length, + // UInt16 language code, and Mac encoded text with no nul termination. + + if ( tmcdInfo->macName.size() > 0xFFFF ) tmcdInfo->macName.erase ( 0xFFFF ); + + ISOMedia::BoxInfo oldNameInfo; + XMP_Assert ( (oldNameInfo.headerSize == 0) && (oldNameInfo.contentSize == 0) ); + if ( tmcdInfo->nameOffset != 0 ) { + const XMP_Uns8 * oldNamePtr = stsdInfo.content + tmcdInfo->nameOffset; + const XMP_Uns8 * oldNameLimit = stsdInfo.content + stsdInfo.contentSize; + (void) ISOMedia::GetBoxInfo ( oldNamePtr, oldNameLimit, &oldNameInfo ); + } + + XMP_Uns32 oldNameBoxSize = (XMP_Uns32)oldNameInfo.headerSize + (XMP_Uns32)oldNameInfo.contentSize; + XMP_Uns32 newNameBoxSize = 0; + if ( ! tmcdInfo->macName.empty() ) newNameBoxSize = 4+4 + 2+2 + (XMP_Uns32)tmcdInfo->macName.size(); + + XMP_Uns32 stsdNewContentSize = stsdInfo.contentSize - oldNameBoxSize + newNameBoxSize; + RawDataBlock stsdNewContent; + stsdNewContent.assign ( stsdNewContentSize, 0 ); // Get the space allocated, direct fill below. + + XMP_Uns32 stsdPrefixSize = tmcdInfo->nameOffset; + if ( tmcdInfo->nameOffset == 0 ) stsdPrefixSize = 4+4 + sizeof ( MOOV_Manager::Content_stsd_entry ); + + XMP_Uns32 oldSuffixOffset = stsdPrefixSize + oldNameBoxSize; + XMP_Uns32 newSuffixOffset = stsdPrefixSize + newNameBoxSize; + XMP_Uns32 stsdSuffixSize = stsdInfo.contentSize - oldSuffixOffset; + + memcpy ( &stsdNewContent[0], stsdInfo.content, stsdPrefixSize ); + if ( stsdSuffixSize != 0 ) memcpy ( &stsdNewContent[newSuffixOffset], (stsdInfo.content + oldSuffixOffset), stsdSuffixSize ); + + XMP_Uns32 newEntrySize = stsdEntrySize - oldNameBoxSize + newNameBoxSize; + MOOV_Manager::Content_stsd_entry * stsdNewEntry = (MOOV_Manager::Content_stsd_entry*) (&stsdNewContent[0] + 8); + PutUns32BE ( newEntrySize, &stsdNewEntry->entrySize ); + + if ( newNameBoxSize != 0 ) { + PutUns32BE ( newNameBoxSize, &stsdNewContent[stsdPrefixSize] ); + PutUns32BE ( ISOMedia::k_name, &stsdNewContent[stsdPrefixSize+4] ); + PutUns16BE ( (XMP_Uns16)tmcdInfo->macName.size(), &stsdNewContent[stsdPrefixSize+8] ); + PutUns16BE ( tmcdInfo->macLang, &stsdNewContent[stsdPrefixSize+10] ); + memcpy ( &stsdNewContent[stsdPrefixSize+12], tmcdInfo->macName.c_str(), tmcdInfo->macName.size() ); + } + + moovMgr->SetBox ( stsdRef, &stsdNewContent[0], stsdNewContentSize ); + + } -static void ExtractMVHD_v0 ( XMP_Uns8 * buffer, MVHD_v1 * mvhd ) // Always convert to the v1 form. +} // ExportTimecodeItems + +// ================================================================================================= +// ImportCr8rItems +// =============== + +#pragma pack ( push, 1 ) + +struct PrmLBoxContent { + XMP_Uns32 magic; + XMP_Uns32 size; + XMP_Uns16 verAPI; + XMP_Uns16 verCode; + XMP_Uns32 exportType; + XMP_Uns16 MacVRefNum; + XMP_Uns32 MacParID; + char filePath[260]; +}; + +enum { kExportTypeMovie = 0, kExportTypeStill = 1, kExportTypeAudio = 2, kExportTypeCustom = 3 }; + +struct Cr8rBoxContent { + XMP_Uns32 magic; + XMP_Uns32 size; + XMP_Uns16 majorVer; + XMP_Uns16 minorVer; + XMP_Uns32 creatorCode; + XMP_Uns32 appleEvent; + char fileExt[16]; + char appOptions[16]; + char appName[32]; +}; + +#pragma pack ( pop ) + +// ------------------------------------------------------------------------------------------------- + +static bool ImportCr8rItems ( const MOOV_Manager & moovMgr, SXMPMeta * xmp ) { - mvhd->version = buffer[0]; - mvhd->flags[0] = buffer[1]; - mvhd->flags[1] = buffer[2]; - mvhd->flags[2] = buffer[3]; - mvhd->creationTime = GetUns32BE ( &buffer[ 4] ); - mvhd->modificationTime = GetUns32BE ( &buffer[ 8] ); - mvhd->timescale = GetUns32BE ( &buffer[12] ); - mvhd->duration = GetUns32BE ( &buffer[16] ); - mvhd->rate = GetUns32BE ( &buffer[20] ); - mvhd->volume = GetUns16BE ( &buffer[24] ); - mvhd->pad_1 = GetUns16BE ( &buffer[26] ); - mvhd->pad_2 = GetUns32BE ( &buffer[28] ); - mvhd->pad_3 = GetUns32BE ( &buffer[32] ); - for ( int i = 0, j = 36; i < 9; ++i, j += 4 ) mvhd->matrix[i] = GetUns32BE ( &buffer[j] ); - for ( int i = 0, j = 72; i < 6; ++i, j += 4 ) mvhd->preDef[i] = GetUns32BE ( &buffer[j] ); - mvhd->nextTrackID = GetUns32BE ( &buffer[96] ); -} + bool haveXMP = false; + + MOOV_Manager::BoxInfo infoPrmL, infoCr8r; + MOOV_Manager::BoxRef refPrmL = moovMgr.GetBox ( "moov/udta/PrmL", &infoPrmL ); + MOOV_Manager::BoxRef refCr8r = moovMgr.GetBox ( "moov/udta/Cr8r", &infoCr8r ); + + bool havePrmL = ( (refPrmL != 0) && (infoPrmL.contentSize == sizeof ( PrmLBoxContent )) ); + bool haveCr8r = ( (refCr8r != 0) && (infoCr8r.contentSize == sizeof ( Cr8rBoxContent )) ); + + if ( havePrmL ) { + + PrmLBoxContent rawPrmL; + XMP_Assert ( sizeof ( rawPrmL ) == 282 ); + XMP_Assert ( sizeof ( rawPrmL.filePath ) == 260 ); + memcpy ( &rawPrmL, infoPrmL.content, sizeof ( rawPrmL ) ); + if ( rawPrmL.magic != 0xBEEFCAFE ) { + Flip4 ( &rawPrmL.exportType ); // The only numeric field that we care about. + } -static void ExtractMVHD_v1 ( XMP_Uns8 * buffer, MVHD_v1 * mvhd ) -{ - mvhd->version = buffer[0]; - mvhd->flags[0] = buffer[1]; - mvhd->flags[1] = buffer[2]; - mvhd->flags[2] = buffer[3]; - mvhd->creationTime = GetUns64BE ( &buffer[ 4] ); - mvhd->modificationTime = GetUns64BE ( &buffer[12] ); - mvhd->timescale = GetUns32BE ( &buffer[20] ); - mvhd->duration = GetUns64BE ( &buffer[24] ); - mvhd->rate = GetUns32BE ( &buffer[32] ); - mvhd->volume = GetUns16BE ( &buffer[36] ); - mvhd->pad_1 = GetUns16BE ( &buffer[38] ); - mvhd->pad_2 = GetUns32BE ( &buffer[40] ); - mvhd->pad_3 = GetUns32BE ( &buffer[44] ); - for ( int i = 0, j = 48; i < 9; ++i, j += 4 ) mvhd->matrix[i] = GetUns32BE ( &buffer[j] ); - for ( int i = 0, j = 84; i < 6; ++i, j += 4 ) mvhd->preDef[i] = GetUns32BE ( &buffer[j] ); - mvhd->nextTrackID = GetUns32BE ( &buffer[108] ); -} + rawPrmL.filePath[259] = 0; // Ensure a terminating nul. + if ( rawPrmL.filePath[0] != 0 ) { + if ( rawPrmL.filePath[0] == '/' ) { + haveXMP = true; + xmp->SetStructField ( kXMP_NS_CreatorAtom, "macAtom", + kXMP_NS_CreatorAtom, "posixProjectPath", rawPrmL.filePath ); + } else if ( XMP_LitNMatch ( rawPrmL.filePath, "\\\\?\\", 4 ) ) { + haveXMP = true; + xmp->SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", + kXMP_NS_CreatorAtom, "uncProjectPath", rawPrmL.filePath ); + } + } + + const char * exportStr = 0; + switch ( rawPrmL.exportType ) { + case kExportTypeMovie : exportStr = "movie"; break; + case kExportTypeStill : exportStr = "still"; break; + case kExportTypeAudio : exportStr = "audio"; break; + case kExportTypeCustom : exportStr = "custom"; break; + } + if ( exportStr != 0 ) { + haveXMP = true; + xmp->SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", exportStr ); + } + + } + + if ( haveCr8r ) { + + Cr8rBoxContent rawCr8r; + XMP_Assert ( sizeof ( rawCr8r ) == 84 ); + XMP_Assert ( sizeof ( rawCr8r.fileExt ) == 16 ); + XMP_Assert ( sizeof ( rawCr8r.appOptions ) == 16 ); + XMP_Assert ( sizeof ( rawCr8r.appName ) == 32 ); + memcpy ( &rawCr8r, infoCr8r.content, sizeof ( rawCr8r ) ); + if ( rawCr8r.magic != 0xBEEFCAFE ) { + Flip4 ( &rawCr8r.creatorCode ); // The only numeric fields that we care about. + Flip4 ( &rawCr8r.appleEvent ); + } + + std::string fieldPath; + + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &fieldPath ); + if ( rawCr8r.creatorCode != 0 ) { + haveXMP = true; + xmp->SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.creatorCode ); // ! Unsigned trickery. + } + + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &fieldPath ); + if ( rawCr8r.appleEvent != 0 ) { + haveXMP = true; + xmp->SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.appleEvent ); // ! Unsigned trickery. + } + + rawCr8r.fileExt[15] = 0; // Ensure a terminating nul. + if ( rawCr8r.fileExt[0] != 0 ) { + haveXMP = true; + xmp->SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", rawCr8r.fileExt ); + } + + rawCr8r.appOptions[15] = 0; // Ensure a terminating nul. + if ( rawCr8r.appOptions[0] != 0 ) { + haveXMP = true; + xmp->SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", rawCr8r.appOptions ); + } + + rawCr8r.appName[31] = 0; // Ensure a terminating nul. + if ( rawCr8r.appName[0] != 0 ) { + haveXMP = true; + xmp->SetProperty ( kXMP_NS_XMP, "CreatorTool", rawCr8r.appName ); + } + + } + + return haveXMP; + +} // ImportCr8rItems // ================================================================================================= -// MPEG4_MetaHandler::ProcessXMP -// ============================= +// ExportCr8rItems +// =============== + +static inline void SetBufferedString ( char * dest, const std::string source, size_t limit ) +{ + memset ( dest, 0, limit ); + size_t count = source.size(); + if ( count >= limit ) count = limit - 1; // Ensure a terminating nul. + memcpy ( dest, source.c_str(), count ); +} -#define kAlmostMaxSeconds 0x7FFFFF00 +// ------------------------------------------------------------------------------------------------- -void MPEG4_MetaHandler::ProcessXMP() +static void ExportCr8rItems ( const SXMPMeta & xmp, MOOV_Manager * moovMgr ) { - if ( this->processedXMP ) return; - this->processedXMP = true; // Make sure only called once. + bool haveNewCr8r = false; + std::string creatorCode, appleEvent, fileExt, appOptions, appName; - if ( this->containsXMP ) { - FillPacketInfo ( this->xmpPacket, &this->packetInfo ); - this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &creatorCode, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &appleEvent, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fileExt, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &appOptions, 0 ); + haveNewCr8r |= xmp.GetProperty ( kXMP_NS_XMP, "CreatorTool", &appName, 0 ); + + MOOV_Manager::BoxInfo infoCr8r; + MOOV_Manager::BoxRef refCr8r = moovMgr->GetBox ( "moov/udta/Cr8r", &infoCr8r ); + bool haveOldCr8r = ( (refCr8r != 0) && (infoCr8r.contentSize == sizeof ( Cr8rBoxContent )) ); + + if ( ! haveNewCr8r ) { + if ( haveOldCr8r ) { + MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", 0 ); + moovMgr->DeleteTypeChild ( udtaRef, 0x43723872 /* 'Cr8r' */ ); + } + return; } - if ( this->mvhdBox.empty() && this->cprtBoxes.empty() ) return; // No legacy, we're done. + Cr8rBoxContent newCr8r; + const Cr8rBoxContent * oldCr8r = (Cr8rBoxContent*) infoCr8r.content; + + if ( ! haveOldCr8r ) { + + memset ( &newCr8r, 0, sizeof(newCr8r) ); + newCr8r.magic = MakeUns32BE ( 0xBEEFCAFE ); + newCr8r.size = MakeUns32BE ( sizeof ( newCr8r ) ); + newCr8r.majorVer = MakeUns16BE ( 1 ); + + } else { + + memcpy ( &newCr8r, oldCr8r, sizeof(newCr8r) ); + if ( GetUns32BE ( &newCr8r.magic ) != 0xBEEFCAFE ) { // Make sure we write BE numbers. + Flip4 ( &newCr8r.magic ); + Flip4 ( &newCr8r.size ); + Flip2 ( &newCr8r.majorVer ); + Flip2 ( &newCr8r.minorVer ); + Flip4 ( &newCr8r.creatorCode ); + Flip4 ( &newCr8r.appleEvent ); + } + + } - std::string oldDigest; - bool oldDigestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "MPEG-4", &oldDigest, 0 ); + if ( ! creatorCode.empty() ) { + newCr8r.creatorCode = MakeUns32BE ( (XMP_Uns32) strtoul ( creatorCode.c_str(), 0, 0 ) ); + } - if ( oldDigestFound ) { - std::string newDigest; - this->MakeLegacyDigest ( &newDigest ); - if ( oldDigest == newDigest ) return; // No legacy changes. + if ( ! appleEvent.empty() ) { + newCr8r.appleEvent = MakeUns32BE ( (XMP_Uns32) strtoul ( appleEvent.c_str(), 0, 0 ) ); } - // If we get here we need to import the legacy metadata. Either there is no old digest in the - // XMP, or the digests differ. In the former case keep any existing XMP, in the latter case take - // new legacy values. So, oldDigestFound means digestsDiffer, else we would have returned. + if ( ! fileExt.empty() ) SetBufferedString ( newCr8r.fileExt, fileExt, sizeof ( newCr8r.fileExt ) ); + if ( ! appOptions.empty() ) SetBufferedString ( newCr8r.appOptions, appOptions, sizeof ( newCr8r.appOptions ) ); + if ( ! appName.empty() ) SetBufferedString ( newCr8r.appName, appName, sizeof ( newCr8r.appName ) ); - // *** The "official" MPEG-4 metadata might not be very interesting. It looks like the 'ilst' - // *** metadata (invented by Apple?) is more interesting. There appears to be no official - // *** documentation. + moovMgr->SetBox ( "moov/udta/Cr8r", &newCr8r, sizeof(newCr8r) ); + +} // ExportCr8rItems - if ( ! this->mvhdBox.empty() ) { +// ================================================================================================= +// GetAtomInfo +// =========== + +struct AtomInfo { + XMP_Int64 atomSize; + XMP_Uns32 atomType; + bool hasLargeSize; +}; + +enum { // ! Do not rearrange, code depends on this order. + kBadQT_NoError = 0, // No errors. + kBadQT_SmallInner = 1, // An extra 1..7 bytes at the end of an inner span. + kBadQT_LargeInner = 2, // More serious inner garbage, found as invalid atom length. + kBadQT_SmallOuter = 3, // An extra 1..7 bytes at the end of the file. + kBadQT_LargeOuter = 4 // More serious EOF garbage, found as invalid atom length. +}; +typedef XMP_Uns8 QTErrorMode; + +static QTErrorMode GetAtomInfo ( const LFA_FileRef qtFile, XMP_Int64 spanSize, int nesting, AtomInfo * info ) +{ + QTErrorMode status = kBadQT_NoError; + XMP_Uns8 buffer [8]; - MVHD_v1 mvhd; - XMP_DateTime xmpDate; + info->hasLargeSize = false; - if ( this->mvhdBox.size() == 100 ) { // *** Should check the version - extract to a static function. - ExtractMVHD_v0 ( (XMP_Uns8*)(this->mvhdBox.data()), &mvhd ); - } else if ( this->mvhdBox.size() == 112 ) { - ExtractMVHD_v1 ( (XMP_Uns8*)(this->mvhdBox.data()), &mvhd ); + LFA_Read ( qtFile, buffer, 8, kLFA_RequireAll ); // Will throw if 8 bytes aren't available. + info->atomSize = GetUns32BE ( &buffer[0] ); // ! Yes, the initial size is big endian UInt32. + info->atomType = GetUns32BE ( &buffer[4] ); + + if ( info->atomSize == 0 ) { // Does the atom extend to EOF? + + if ( nesting != 0 ) return kBadQT_LargeInner; + info->atomSize = spanSize; // This outer atom goes to EOF. + + } else if ( info->atomSize == 1 ) { // Does the atom have a 64-bit size? + + if ( spanSize < 16 ) { // Is there room in the span for the 16 byte header? + status = kBadQT_LargeInner; + if ( nesting == 0 ) status += 2; // Convert to "outer". + return status; } - if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "CreateDate" )) ) { - if ( (mvhd.creationTime >> 32) < 0xFF ) { // Sanity check for bogus date info. + LFA_Read ( qtFile, buffer, 8, kLFA_RequireAll ); + info->atomSize = (XMP_Int64) GetUns64BE ( &buffer[0] ); + info->hasLargeSize = true; - memset ( &xmpDate, 0, sizeof(xmpDate) ); // AUDIT: Using sizeof(xmpDate) is safe. - xmpDate.year = 1904; // Start at midnight, January 1 1904, UTC - xmpDate.month = 1; // ! Note that the XMP binary fields are signed to allow - xmpDate.day = 1; // ! offsets and normalization in both directions. + } - while ( mvhd.creationTime > kAlmostMaxSeconds ) { - xmpDate.second += kAlmostMaxSeconds; - SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect. - mvhd.creationTime -= kAlmostMaxSeconds; - } - xmpDate.second += (XMP_Uns32)mvhd.creationTime; - SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect. - - this->xmpObj.SetProperty_Date ( kXMP_NS_XMP, "CreateDate", xmpDate ); - this->containsXMP = true; + XMP_Assert ( status == kBadQT_NoError ); + return status; - } +} // GetAtomInfo + +// ================================================================================================= +// CheckAtomList +// ============= +// +// Check that a sequence of atoms fills a given span. The I/O position must be at the start of the +// span, it is left just past the span on success. Recursive checks are done for top level 'moov' +// atoms, and second level 'udta' atoms ('udta' inside 'moov'). +// +// Checking continues for "small inner" errors. They will be reported if no other kinds of errors +// are found, otherwise the other error is reported. Checking is immediately aborted for any "large" +// error. The rationale is that QuickTime can apparently handle small inner errors. They might be +// arise from updates that shorten an atom by less than 8 bytes. Larger shrinkage should introduce a +// 'free' atom. + +static QTErrorMode CheckAtomList ( const LFA_FileRef qtFile, XMP_Int64 spanSize, int nesting ) +{ + QTErrorMode status = kBadQT_NoError; + AtomInfo info; + + const static XMP_Uns32 moovAtomType = 0x6D6F6F76; // ! Don't use MakeUns32BE, already big endian. + const static XMP_Uns32 udtaAtomType = 0x75647461; + + for ( ; spanSize >= 8; spanSize -= info.atomSize ) { + + QTErrorMode atomStatus = GetAtomInfo ( qtFile, spanSize, nesting, &info ); + if ( atomStatus != kBadQT_NoError ) return atomStatus; + + XMP_Int64 headerSize = 8; + if ( info.hasLargeSize ) headerSize = 16; + + if ( (info.atomSize < headerSize) || (info.atomSize > spanSize) ) { + status = kBadQT_LargeInner; + if ( nesting == 0 ) status += 2; // Convert to "outer". + return status; } - if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "ModifyDate" )) ) { - if ( (mvhd.modificationTime >> 32) < 0xFF ) { // Sanity check for bogus date info. + bool doChildren = false; + if ( (nesting == 0) && (info.atomType == moovAtomType) ) doChildren = true; + if ( (nesting == 1) && (info.atomType == udtaAtomType) ) doChildren = true; - memset ( &xmpDate, 0, sizeof(xmpDate) ); // AUDIT: Using sizeof(xmpDate) is safe. - xmpDate.year = 1904; // Start at midnight, January 1 1904, UTC - xmpDate.month = 1; - xmpDate.day = 1; + XMP_Int64 dataSize = info.atomSize - headerSize; - while ( mvhd.modificationTime > kAlmostMaxSeconds ) { - xmpDate.second += kAlmostMaxSeconds; - SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect. - mvhd.modificationTime -= kAlmostMaxSeconds; - } - xmpDate.second += (XMP_Uns32)mvhd.modificationTime; - SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect. - - this->xmpObj.SetProperty_Date ( kXMP_NS_XMP, "ModifyDate", xmpDate ); - this->containsXMP = true; + if ( ! doChildren ) { + LFA_Seek ( qtFile, dataSize, SEEK_CUR ); + } else { + QTErrorMode innerStatus = CheckAtomList ( qtFile, dataSize, nesting+1 ); + if ( innerStatus > kBadQT_SmallInner ) return innerStatus; // Quit for serious errors. + if ( status == kBadQT_NoError ) status = innerStatus; // Remember small inner errors. + } - } + } + + XMP_Assert ( status <= kBadQT_SmallInner ); // Else already returned. + // ! Make sure inner kBadQT_SmallInner is propagated if this span is OK. + + if ( spanSize != 0 ) { + LFA_Seek ( qtFile, spanSize, SEEK_CUR ); // ! Skip the trailing garbage of this span. + status = kBadQT_SmallInner; + if ( spanSize >= 8 ) status = kBadQT_LargeInner; + if ( nesting == 0 ) status += 2; // Convert to "outer". + } + + return status; + +} // CheckAtomList + +// ================================================================================================= +// AttemptFileRepair +// ================= + +static void AttemptFileRepair ( LFA_FileRef qtFile, XMP_Int64 fileSpace, QTErrorMode status ) +{ + + switch ( status ) { + case kBadQT_NoError : return; // Sanity check. + case kBadQT_SmallInner : return; // Fixed in normal update code for the 'udta' box. + case kBadQT_LargeInner : XMP_Throw ( "Can't repair QuickTime file", kXMPErr_BadFileFormat ); + case kBadQT_SmallOuter : break; // Truncate file below. + case kBadQT_LargeOuter : break; // Truncate file below. + default : XMP_Throw ( "Invalid QuickTime error mode", kXMPErr_InternalFailure ); + } + + AtomInfo info; + XMP_Int64 headerSize; + + // Process the top level atoms until an error is found. + + LFA_Seek ( qtFile, 0, SEEK_SET ); + + for ( ; fileSpace >= 8; fileSpace -= info.atomSize ) { + + QTErrorMode atomStatus = GetAtomInfo ( qtFile, fileSpace, 0, &info ); + + headerSize = 8; // ! Set this before checking atomStatus, used after the loop. + if ( info.hasLargeSize ) headerSize = 16; + + if ( atomStatus != kBadQT_NoError ) break; + if ( (info.atomSize < headerSize) || (info.atomSize > fileSpace) ) break; + + XMP_Int64 dataSize = info.atomSize - headerSize; + LFA_Seek ( qtFile, dataSize, SEEK_CUR ); + + } + + // Truncate the file. If fileSpace >= 8 then the loop exited early due to a bad atom, seek back + // to the atom's start. Otherwise, the loop exited because no more atoms are possible, no seek. + + if ( fileSpace >= 8 ) LFA_Seek ( qtFile, -headerSize, SEEK_CUR ); + XMP_Int64 currPos = LFA_Tell ( qtFile ); + LFA_Truncate ( qtFile, currPos ); + +} // AttemptFileRepair + +// ================================================================================================= +// CheckQTFileStructure +// ==================== + +static void CheckQTFileStructure ( XMPFileHandler * thiz, bool doRepair ) +{ + XMPFiles * parent = thiz->parent; + LFA_FileRef fileRef = parent->fileRef; + XMP_Int64 fileSize = LFA_Measure ( fileRef ); + + // Check the basic file structure and try to repair if asked. + + LFA_Seek ( fileRef, 0, SEEK_SET ); + QTErrorMode status = CheckAtomList ( fileRef, fileSize, 0 ); + + if ( status != kBadQT_NoError ) { + if ( doRepair || (status == kBadQT_SmallInner) || (status == kBadQT_SmallOuter) ) { + AttemptFileRepair ( fileRef, fileSize, status ); // Will throw if the attempt fails. + } else if ( status != kBadQT_SmallInner ) { + XMP_Throw ( "Ill-formed QuickTime file", kXMPErr_BadFileFormat ); + } else { + return; // ! Ignore these, QT seems to be able to handle them. + // *** Might want to throw for check-only, ignore when repairing. + } + } + +} // CheckQTFileStructure; + +// ================================================================================================= +// CheckFinalBox +// ============= +// +// Before appending anything new, check if the final top level box has a "to EoF" length. If so, fix +// it to have an explicit length. + +static void CheckFinalBox ( LFA_FileRef fileRef, XMP_AbortProc abortProc, void * abortArg ) +{ + const bool checkAbort = (abortProc != 0); + + XMP_Uns64 fileSize = LFA_Measure ( fileRef ); + + // Find the last 2 boxes in the file. Need the previous to last in case it is an Apple 'wide' box. + + XMP_Uns64 prevPos, lastPos, nextPos; + ISOMedia::BoxInfo prevBox, lastBox; + XMP_Uns8 buffer [16]; // Enough to create an extended header. + + memset ( &prevBox, 0, sizeof(prevBox) ); // AUDIT: Using sizeof(prevBox) is safe. + memset ( &lastBox, 0, sizeof(lastBox) ); // AUDIT: Using sizeof(lastBox) is safe. + prevPos = lastPos = nextPos = 0; + while ( nextPos != fileSize ) { + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_MetaHandler::CheckFinalBox - User abort", kXMPErr_UserAbort ); + } + prevBox = lastBox; + prevPos = lastPos; + lastPos = nextPos; + nextPos = ISOMedia::GetBoxInfo ( fileRef, lastPos, fileSize, &lastBox, true /* throw errors */ ); + } + + // See if the last box is valid and has a "to EoF" size. + + if ( lastBox.headerSize < 8 ) XMP_Throw ( "MPEG-4 final box is invalid", kXMPErr_EnforceFailure ); + LFA_Seek ( fileRef, lastPos, SEEK_SET ); + LFA_Read ( fileRef, buffer, 4 ); + XMP_Uns64 lastSize = GetUns32BE ( &buffer[0] ); // ! Yes, the file has a 32-bit value. + if ( lastSize != 0 ) return; + + // Have a final "to EoF" box, try to write the explicit size. + + lastSize = lastBox.headerSize + lastBox.contentSize; + if ( lastSize <= 0xFFFFFFFFUL ) { + + // Fill in the 32-bit exact size. + PutUns32BE ( (XMP_Uns32)lastSize, &buffer[0] ); + LFA_Seek ( fileRef, lastPos, SEEK_SET ); + LFA_Write ( fileRef, buffer, 4 ); + + } else { + + // Try to convert to using an extended header. + + if ( (prevBox.boxType != ISOMedia::k_wide) || (prevBox.headerSize != 8) || (prevBox.contentSize != 0) ) { + XMP_Throw ( "Can't expand final box header", kXMPErr_EnforceFailure ); } + XMP_Assert ( prevPos == (lastPos - 8) ); + + PutUns32BE ( 1, &buffer[0] ); + PutUns32BE ( lastBox.boxType, &buffer[4] ); + PutUns64BE ( lastSize, &buffer[8] ); + LFA_Seek ( fileRef, prevPos, SEEK_SET ); + LFA_Write ( fileRef, buffer, 16 ); + + } + +} // CheckFinalBox + +// ================================================================================================= +// WriteBoxHeader +// ============== + +static void WriteBoxHeader ( LFA_FileRef fileRef, XMP_Uns32 boxType, XMP_Uns64 boxSize ) +{ + XMP_Uns32 u32; + XMP_Uns64 u64; + XMP_Enforce ( boxSize >= 8 ); // The size must be the full size, not just the content. + + if ( boxSize <= 0xFFFFFFFF ) { + + u32 = MakeUns32BE ( (XMP_Uns32)boxSize ); + LFA_Write ( fileRef, &u32, 4 ); + u32 = MakeUns32BE ( boxType ); + LFA_Write ( fileRef, &u32, 4 ); + + } else { + + u32 = MakeUns32BE ( 1 ); + LFA_Write ( fileRef, &u32, 4 ); + u32 = MakeUns32BE ( boxType ); + LFA_Write ( fileRef, &u32, 4 ); + u64 = MakeUns64BE ( boxSize ); + LFA_Write ( fileRef, &u64, 8 ); + + } + +} // WriteBoxHeader + +// ================================================================================================= +// WipeBoxFree +// =========== +// +// Change the box's type to 'free' (or create a 'free' box) and zero the content. - if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) { - if ( mvhd.timescale != 0 ) { // Avoid 1/0 for the scale field. - char buffer [32]; // A 64-bit number is at most 20 digits. - this->xmpObj.DeleteProperty ( kXMP_NS_DM, "duration" ); // Delete the whole struct. - snprintf ( buffer, sizeof(buffer), "%llu", mvhd.duration ); // AUDIT: The buffer is big enough. - this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", &buffer[0] ); - snprintf ( buffer, sizeof(buffer), "1/%u", mvhd.timescale ); // AUDIT: The buffer is big enough. - this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", &buffer[0] ); - this->containsXMP = true; +static XMP_Uns8 kZeroes [64*1024]; // C semantics guarantee zero initialization. + +static void WipeBoxFree ( LFA_FileRef fileRef, XMP_Uns64 boxOffset, XMP_Uns32 boxSize ) +{ + if ( boxSize == 0 ) return; + XMP_Enforce ( boxSize >= 8 ); + + LFA_Seek ( fileRef, boxOffset, SEEK_SET ); + XMP_Uns32 u32; + u32 = MakeUns32BE ( boxSize ); // ! The actual size should not change, but might have had a long header. + LFA_Write ( fileRef, &u32, 4 ); + u32 = MakeUns32BE ( ISOMedia::k_free ); + LFA_Write ( fileRef, &u32, 4 ); + + XMP_Uns32 ioCount = sizeof ( kZeroes ); + for ( boxSize -= 8; boxSize > 0; boxSize -= ioCount ) { + if ( ioCount > boxSize ) ioCount = boxSize; + LFA_Write ( fileRef, &kZeroes[0], ioCount ); + } + +} // WipeBoxFree + +// ================================================================================================= +// CreateFreeSpaceList +// =================== + +struct SpaceInfo { + XMP_Uns64 offset, size; + SpaceInfo() : offset(0), size(0) {}; + SpaceInfo ( XMP_Uns64 _offset, XMP_Uns64 _size ) : offset(_offset), size(_size) {}; +}; + +typedef std::vector<SpaceInfo> FreeSpaceList; + +static void CreateFreeSpaceList ( LFA_FileRef fileRef, XMP_Uns64 fileSize, + XMP_Uns64 oldOffset, XMP_Uns32 oldSize, FreeSpaceList * spaceList ) +{ + XMP_Uns64 boxPos, boxNext, adjacentFree; + ISOMedia::BoxInfo currBox; + + LFA_Seek ( fileRef, 0, SEEK_SET ); + spaceList->clear(); + + for ( boxPos = 0; boxPos < fileSize; boxPos = boxNext ) { + + boxNext = ISOMedia::GetBoxInfo ( fileRef, boxPos, fileSize, &currBox, true /* throw errors */ ); + XMP_Uns64 currSize = currBox.headerSize + currBox.contentSize; + + if ( (currBox.boxType == ISOMedia::k_free) || + (currBox.boxType == ISOMedia::k_skip) || + ((boxPos == oldOffset) && (currSize == oldSize)) ) { + + if ( spaceList->empty() || (boxPos != adjacentFree) ) { + spaceList->push_back ( SpaceInfo ( boxPos, currSize ) ); + adjacentFree = boxPos + currSize; + } else { + SpaceInfo * lastSpace = &spaceList->back(); + lastSpace->size += currSize; } + } + + } + +} // CreateFreeSpaceList + +// ================================================================================================= +// MPEG4_MetaHandler::CacheFileData +// ================================ +// +// There are 3 file variants: normal ISO Base Media, modern QuickTime, and classic QuickTime. The +// XMP is placed differently between the ISO and two QuickTime forms, and there is different but not +// colliding native metadata. The entire 'moov' subtree is cached, along with the top level 'uuid' +// box of XMP if present. + +void MPEG4_MetaHandler::CacheFileData() +{ + XMP_Assert ( ! this->containsXMP ); + + XMPFiles * parent = this->parent; + XMP_OptionBits openFlags = parent->openFlags; + + LFA_FileRef fileRef = parent->fileRef; + + XMP_AbortProc abortProc = parent->abortProc; + void * abortArg = parent->abortArg; + const bool checkAbort = (abortProc != 0); + + // First do some special case repair to QuickTime files, based on bad files in the wild. + const bool isUpdate = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenForUpdate ); + const bool doRepair = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenRepairFile ); + + if ( isUpdate && (parent->format == kXMP_MOVFile) ) { + CheckQTFileStructure ( this, doRepair ); // Will throw for failure. } - if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "rights" )) ) { + // Cache the top level 'moov' and 'uuid'/XMP boxes. - std::string tempStr; - char lang3 [4]; // The unpacked ISO-639-2/T language code. - lang3[3] = 0; + XMP_Uns64 fileSize = LFA_Measure ( fileRef ); - for ( size_t i = 0, limit = this->cprtBoxes.size(); i < limit; ++i ) { - if ( this->cprtBoxes[i].size() < 7 ) continue; + XMP_Uns64 boxPos, boxNext; + ISOMedia::BoxInfo currBox; - const XMP_Uns8 * currCprt = (XMP_Uns8*) (this->cprtBoxes[i].c_str()); // ! Actually structured data! - size_t rawLen = this->cprtBoxes[i].size() - 6; - if ( currCprt[0] != 0 ) continue; // Only proceed for version 0, ignore the flags. + bool xmpOnly = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenOnlyXMP ); + bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO); - XMP_Uns16 packedLang = GetUns16BE ( &currCprt[4] ); - lang3[0] = (packedLang >> 10) | 0x60; - lang3[1] = ((packedLang >> 5) & 0x1F) | 0x60; - lang3[2] = (packedLang & 0x1F) | 0x60; - - XMP_StringPtr xmpLang = Lookup2LetterLang ( lang3 ); - XMP_StringPtr xmpValue = (XMP_StringPtr) &currCprt[6]; + bool uuidFound = (! haveISOFile); // Ignore the XMP 'uuid' box for QuickTime files. + bool moovIgnored = (xmpOnly & haveISOFile); // Ignore the 'moov' box for XMP-only ISO files. + bool moovFound = moovIgnored; + + for ( boxPos = 0; boxPos < fileSize; boxPos = boxNext ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); + } + + + boxNext = ISOMedia::GetBoxInfo ( fileRef, boxPos, fileSize, &currBox ); + + if ( (! moovFound) && (currBox.boxType == ISOMedia::k_moov) ) { - if ( (rawLen >= 8) && (GetUns16BE ( xmpValue ) == 0xFEFF) ) { - FromUTF16 ( (UTF16Unit*)xmpValue, rawLen/2, &tempStr, true /* big endian */ ); - xmpValue = tempStr.c_str(); + XMP_Uns64 fullMoovSize = currBox.headerSize + currBox.contentSize; + if ( fullMoovSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe. + XMP_Throw ( "Oversize 'moov' box", kXMPErr_EnforceFailure ); } - this->xmpObj.SetLocalizedText ( kXMP_NS_DC, "rights", xmpLang, "", xmpValue ); - this->containsXMP = true; + this->moovMgr.fullSubtree.assign ( (XMP_Uns32)fullMoovSize, 0 ); + LFA_Seek ( fileRef, boxPos, SEEK_SET ); + LFA_Read ( fileRef, &this->moovMgr.fullSubtree[0], (XMP_Uns32)fullMoovSize ); + + this->moovBoxPos = boxPos; + this->moovBoxSize = (XMP_Uns32)fullMoovSize; + moovFound = true; + if ( uuidFound ) break; // Exit the loop when both are found. + + } else if ( (! uuidFound) && (currBox.boxType == ISOMedia::k_uuid) ) { + + if ( currBox.contentSize < 16 ) continue; + + XMP_Uns8 uuid [16]; + LFA_Read ( fileRef, uuid, 16, kLFA_RequireAll ); + if ( memcmp ( uuid, ISOMedia::k_xmpUUID, 16 ) != 0 ) continue; // Check for the XMP GUID. + + XMP_Uns64 fullUuidSize = currBox.headerSize + currBox.contentSize; + if ( fullUuidSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe. + XMP_Throw ( "Oversize XMP 'uuid' box", kXMPErr_EnforceFailure ); + } + + this->packetInfo.offset = boxPos + currBox.headerSize + 16; // The 16 is for the UUID. + this->packetInfo.length = (XMP_Int32) (currBox.contentSize - 16); + + this->xmpPacket.assign ( this->packetInfo.length, ' ' ); + LFA_Read ( fileRef, (void*)this->xmpPacket.data(), this->packetInfo.length, kLFA_RequireAll ); + + this->xmpBoxPos = boxPos; + this->xmpBoxSize = (XMP_Uns32)fullUuidSize; + uuidFound = true; + if ( moovFound ) break; // Exit the loop when both are found. } } + + if ( (! moovFound) && (! moovIgnored) ) XMP_Throw ( "No 'moov' box", kXMPErr_BadFileFormat ); -} // MPEG4_MetaHandler::ProcessXMP +} // MPEG4_MetaHandler::CacheFileData // ================================================================================================= -// MPEG4_MetaHandler::PickNewLocation -// ================================== -// -// Pick a new location for the XMP. This is the first available 'free' space before any 'mdat' box, -// otherwise the end of the file. Any existing XMP 'uuid' box has already been marked as 'free'. +// MPEG4_MetaHandler::ProcessXMP +// ============================= -void MPEG4_MetaHandler::PickNewLocation() +void MPEG4_MetaHandler::ProcessXMP() { - LFA_FileRef fileRef = this->parent->fileRef; - XMP_Uns64 fileSize = LFA_Measure ( fileRef ); + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. - XMP_Uns32 xmpBoxSize = 4+4+16 + (XMP_Uns32)this->xmpPacket.size(); + XMPFiles * parent = this->parent; + XMP_OptionBits openFlags = parent->openFlags; - XMP_Uns64 currPos, prevPos; // Info about the most recent 2 boxes. - XMP_Uns32 currType, prevType; - XMP_Uns64 currSize, prevSize, hSize, cSize; + bool xmpOnly = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenOnlyXMP ); + bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO); + + // Process the cached XMP (from the 'uuid' box) if that is all we want and this is an ISO file. + + if ( xmpOnly & haveISOFile ) { + + this->containsXMP = this->havePreferredXMP = (this->packetInfo.length != 0); + + if ( this->containsXMP ) { + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + this->xmpObj.DeleteProperty ( kXMP_NS_XMP, "NativeDigests" ); // No longer used. + } + + return; - XMP_Uns32 be32Size; - XMP_Uns64 be64Size; + } - XMP_AbortProc abortProc = this->parent->abortProc; - void * abortArg = this->parent->abortArg; - const bool checkAbort = (abortProc != 0); + // Parse the cached 'moov' subtree, parse the preferred XMP. - bool pastMDAT = false; + if ( this->moovMgr.fullSubtree.empty() ) XMP_Throw ( "No 'moov' box", kXMPErr_BadFileFormat ); + this->moovMgr.ParseMemoryTree ( this->fileMode ); - currType = 0; - prevPos = prevSize = currSize = 0; - for ( currPos = 0; currPos < fileSize; currPos += currSize ) { + if ( (this->xmpBoxPos == 0) || (! haveISOFile) ) { - if ( checkAbort && abortProc(abortArg) ) { - XMP_Throw ( "MPEG4_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort ); - } + // Look for the QuickTime moov/uuid/XMP_ box. - prevPos += prevSize; // ! We'll go through at least once, the first box is 'ftyp'. - prevType = currType; // ! Care is needed to preserve the prevBox and currBox info when - prevSize = currSize; // ! the loop exits because it hits EoF. - XMP_Assert ( (prevPos + prevSize) == currPos ); + MOOV_Manager::BoxInfo xmpInfo; + MOOV_Manager::BoxRef xmpRef = this->moovMgr.GetBox ( "moov/udta/XMP_", &xmpInfo ); - GetBoxInfo ( fileRef, fileSize, currPos, &currType, &hSize, &cSize ); - currSize = hSize + cSize; + if ( (xmpRef != 0) && (xmpInfo.contentSize != 0) ) { - if ( pastMDAT ) continue; // Keep scanning to the end of the file. - if ( currType == kBE_mdat ) pastMDAT = true; - if ( currType != kBE_free ) continue; - if ( currSize >= xmpBoxSize ) break; + this->xmpBoxPos = this->moovBoxPos + this->moovMgr.GetParsedOffset ( xmpRef ); + this->packetInfo.offset = this->xmpBoxPos + this->moovMgr.GetHeaderSize ( xmpRef ); + this->packetInfo.length = xmpInfo.contentSize; + + this->xmpPacket.assign ( (char*)xmpInfo.content, this->packetInfo.length ); + this->havePreferredXMP = (! haveISOFile); - if ( prevType == kBE_free ) { - // If we get here the prevBox and currBox are both 'free' and neither big enough alone. - // Pretend to combine them for this check and for possible following checks. We don't - // really compbine them, we just remember the start and total length. - currPos = prevPos; - currSize += prevSize; - prevSize = 0; // ! For the start of the next loop pass. - if ( currSize >= xmpBoxSize ) break; } - + + } + + if ( this->xmpBoxPos != 0 ) { + this->containsXMP = true; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + this->xmpObj.DeleteProperty ( kXMP_NS_XMP, "NativeDigests" ); // No longer used. } - if ( currPos < fileSize ) { + // Import the non-XMP items. Do the imports in reverse priority order, last import wins! + + MOOV_Manager::BoxInfo mvhdInfo; + MOOV_Manager::BoxRef mvhdRef = this->moovMgr.GetBox ( "moov/mvhd", &mvhdInfo ); + bool mvhdFound = ((mvhdRef != 0) && (mvhdInfo.contentSize != 0)); + + MOOV_Manager::BoxInfo udtaInfo; + MOOV_Manager::BoxRef udtaRef = this->moovMgr.GetBox ( "moov/udta", &udtaInfo ); + std::vector<MOOV_Manager::BoxInfo> cprtBoxes; + + if ( udtaRef != 0 ) { + for ( XMP_Uns32 i = 0; i < udtaInfo.childCount; ++i ) { + MOOV_Manager::BoxInfo currInfo; + MOOV_Manager::BoxRef currRef = this->moovMgr.GetNthChild ( udtaRef, i, &currInfo ); + if ( currRef == 0 ) break; // Sanity check, should not happen. + if ( currInfo.boxType != ISOMedia::k_cprt ) continue; + cprtBoxes.push_back ( currInfo ); + } + } + bool cprtFound = (! cprtBoxes.empty()); + + bool tradQTFound = this->tradQTMgr.ParseCachedBoxes ( this->moovMgr ); + bool tmcdFound = this->ParseTimecodeTrack(); - // Put the XMP at the start of the 'free' space. Increase the size of the XMP if the excess - // is less than 8 bytes, otherwise write a new 'free' box header. + if ( this->fileMode == MOOV_Manager::kFileIsNormalISO ) { + + if ( mvhdFound ) this->containsXMP |= ImportMVHDItems ( mvhdInfo, &this->xmpObj ); + if ( cprtFound ) this->containsXMP |= ImportISOCopyrights ( cprtBoxes, &this->xmpObj ); + } else { // This is a QuickTime file, either traditional or modern. + + if ( mvhdFound ) this->containsXMP |= ImportMVHDItems ( mvhdInfo, &this->xmpObj ); + if ( cprtFound ) this->containsXMP |= ImportISOCopyrights ( cprtBoxes, &this->xmpObj ); + if ( tmcdFound | tradQTFound ) { + // Some of the timecode items are in the .../udta/©... set but handled by ImportTimecodeItems. + this->containsXMP |= ImportTimecodeItems ( this->tmcdInfo, this->tradQTMgr, &this->xmpObj ); + } + + this->containsXMP |= ImportCr8rItems ( this->moovMgr, &this->xmpObj ); + + } + +} // MPEG4_MetaHandler::ProcessXMP + +// ================================================================================================= +// MPEG4_MetaHandler::ParseTimecodeTrack +// ===================================== + +bool MPEG4_MetaHandler::ParseTimecodeTrack() +{ + MOOV_Manager::BoxRef stblRef = FindTimecodeTrack ( this->moovMgr ); + if ( stblRef == 0 ) return false; + + // Find the .../stbl/stsd box and process the first table entry. + + MOOV_Manager::BoxInfo stsdInfo; + MOOV_Manager::BoxRef stsdRef; + + stsdRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stsd, &stsdInfo ); + if ( stsdRef == 0 ) return false; + if ( stsdInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsd_entry )) ) return false; + if ( GetUns32BE ( stsdInfo.content + 4 ) == 0 ) return false; // Make sure the entry count is non-zero. + + const MOOV_Manager::Content_stsd_entry * stsdRawEntry = (MOOV_Manager::Content_stsd_entry*) (stsdInfo.content + 8); + + XMP_Uns32 stsdEntrySize = GetUns32BE ( &stsdRawEntry->entrySize ); + if ( stsdEntrySize > (stsdInfo.contentSize - 4) ) stsdEntrySize = stsdInfo.contentSize - 4; + if ( stsdEntrySize < sizeof ( MOOV_Manager::Content_stsd_entry ) ) return false; + + XMP_Uns32 stsdEntryFormat = GetUns32BE ( &stsdRawEntry->format ); + if ( stsdEntryFormat != ISOMedia::k_tmcd ) return false; + + this->tmcdInfo.timeScale = GetUns32BE ( &stsdRawEntry->timeScale ); + this->tmcdInfo.frameDuration = GetUns32BE ( &stsdRawEntry->frameDuration ); + + XMP_Uns32 flags = GetUns32BE ( &stsdRawEntry->flags ); + this->tmcdInfo.isDropFrame = flags & 0x1; + + // Look for a trailing 'name' box on the first stsd table entry. + + XMP_Uns32 stsdTrailerSize = stsdEntrySize - sizeof ( MOOV_Manager::Content_stsd_entry ); + if ( stsdTrailerSize > 8 ) { // Room for a non-empty 'name' box? + + const XMP_Uns8 * trailerStart = stsdInfo.content + 8 + sizeof ( MOOV_Manager::Content_stsd_entry ); + const XMP_Uns8 * trailerLimit = trailerStart + stsdTrailerSize; + const XMP_Uns8 * trailerPos; + const XMP_Uns8 * trailerNext; + ISOMedia::BoxInfo trailerInfo; - this->xmpBoxPos = currPos; - XMP_Assert ( (currType == kBE_free) && (currSize >= xmpBoxSize) ); + for ( trailerPos = trailerStart; trailerPos < trailerLimit; trailerPos = trailerNext ) { + + trailerNext = ISOMedia::GetBoxInfo ( trailerPos, trailerLimit, &trailerInfo ); + + if ( trailerInfo.boxType == ISOMedia::k_name ) { + + this->tmcdInfo.nameOffset = (XMP_Uns32) (trailerPos - stsdInfo.content); + + if ( trailerInfo.contentSize > 4 ) { + + XMP_Uns16 textLen = GetUns16BE ( trailerPos + trailerInfo.headerSize ); + this->tmcdInfo.macLang = GetUns16BE ( trailerPos + trailerInfo.headerSize + 2 ); + + if ( trailerInfo.contentSize >= (XMP_Uns64)(textLen + 4) ) { + const char * textPtr = (char*) (trailerPos + trailerInfo.headerSize + 4); + this->tmcdInfo.macName.assign ( textPtr, textLen ); + } + + } + + break; // Done after finding the first 'name' box. - XMP_Uns64 excessSpace = currSize - xmpBoxSize; - if ( excessSpace < 8 ) { - this->xmpPacket.append ( (size_t)excessSpace, ' ' ); - } else { - LFA_Seek ( fileRef, (currPos + xmpBoxSize), SEEK_SET ); - if ( excessSpace <= 0xFFFFFFFFULL ) { - be32Size = MakeUns32BE ( (XMP_Uns32)excessSpace ); - LFA_Write ( fileRef, &be32Size, 4 ); - LFA_Write ( fileRef, &kBE_free, 4 ); - } else { - be32Size = MakeUns32BE ( 1 ); - be64Size = MakeUns64BE ( excessSpace ); - LFA_Write ( fileRef, &be32Size, 4 ); - LFA_Write ( fileRef, &kBE_free, 4 ); - LFA_Write ( fileRef, &be64Size, 8 ); } + } + } + + // Find the timecode sample. + + XMP_Uns64 sampleOffset = 0; + MOOV_Manager::BoxInfo tempInfo; + MOOV_Manager::BoxRef tempRef; + + tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stsc, &tempInfo ); + if ( tempRef == 0 ) return false; + if ( tempInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsc_entry )) ) return false; + if ( GetUns32BE ( tempInfo.content + 4 ) == 0 ) return false; // Make sure the entry count is non-zero. + + XMP_Uns32 firstChunkNumber = GetUns32BE ( tempInfo.content + 8 ); // Want first field of first entry. + + tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stco, &tempInfo ); + + if ( tempRef != 0 ) { + + if ( tempInfo.contentSize < (8 + 4) ) return false; + XMP_Uns32 stcoCount = GetUns32BE ( tempInfo.content + 4 ); + if ( stcoCount < firstChunkNumber ) return false; + XMP_Uns32 * stcoPtr = (XMP_Uns32*) (tempInfo.content + 8); + sampleOffset = GetUns32BE ( &stcoPtr[firstChunkNumber-1] ); // ! Chunk number is 1-based. + } else { - // Appending the XMP, make sure the current final box has an explicit size. + 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. Need to reopen the file if the XMPFile was open for read-only, + // normally all I/O is done within CacheFileData. + + bool openForRead = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForRead); - this->xmpBoxPos = fileSize; - XMP_Assert ( currPos == fileSize ); + LFA_FileRef fileRef = this->parent->fileRef; + if ( openForRead ) { + XMP_Assert ( fileRef == 0 ); + fileRef = LFA_Open ( this->parent->filePath.c_str(), 'r' ); + } + + if ( fileRef != 0 ) { // The reopen might have failed. + LFA_Seek ( fileRef, sampleOffset, SEEK_SET ); + LFA_Read ( fileRef, &this->tmcdInfo.timecodeSample, 4, kLFA_RequireAll ); + this->tmcdInfo.timecodeSample = MakeUns32BE ( this->tmcdInfo.timecodeSample ); + if ( openForRead ) LFA_Close ( fileRef ); + } - currPos -= currSize; // Move back to the final box's origin. - LFA_Seek ( fileRef, currPos, SEEK_SET ); - LFA_Read ( fileRef, &be32Size, 4, kLFA_RequireAll ); - be32Size = MakeUns32BE ( be32Size ); + } + + // Finally update this->tmcdInfo to remember (for update) that there is an OK timecode track. + + this->tmcdInfo.stsdBoxFound = true; + this->tmcdInfo.sampleOffset = sampleOffset; + return true; - if ( be32Size == 0 ) { +} // MPEG4_MetaHandler::ParseTimecodeTrack + +// ================================================================================================= +// MPEG4_MetaHandler::UpdateTopLevelBox +// ==================================== + +void MPEG4_MetaHandler::UpdateTopLevelBox ( XMP_Uns64 oldOffset, XMP_Uns32 oldSize, + const XMP_Uns8 * newBox, XMP_Uns32 newSize ) +{ + if ( (oldSize == 0) && (newSize == 0) ) return; // Sanity check, should not happen. + + LFA_FileRef fileRef = this->parent->fileRef; + XMP_Uns64 oldFileSize = LFA_Measure ( fileRef ); + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + + if ( newSize == oldSize ) { + + // Trivial case, update the existing box in-place. + LFA_Seek ( fileRef, oldOffset, SEEK_SET ); + LFA_Write ( fileRef, newBox, oldSize ); + + } else if ( (oldOffset + oldSize) == oldFileSize ) { + + // The old box was at the end, write the new and truncate the file if necessary. + LFA_Seek ( fileRef, oldOffset, SEEK_SET ); + LFA_Write ( fileRef, newBox, newSize ); + LFA_Truncate ( fileRef, (oldOffset + newSize) ); // Does nothing if new size is bigger. + + } else if ( (newSize < oldSize) && ((oldSize - newSize) >= 8) ) { + + // The new size is smaller and there is enough room to create a free box. + LFA_Seek ( fileRef, oldOffset, SEEK_SET ); + LFA_Write ( fileRef, newBox, newSize ); + WipeBoxFree ( fileRef, (oldOffset + newSize), (oldSize - newSize) ); + + } else { + + // Look for a trailing free box with enough space. If not found, consider any free space. + // If still not found, append the new box and make the old one free. + + ISOMedia::BoxInfo nextBoxInfo; + (void) ISOMedia::GetBoxInfo ( fileRef, (oldOffset + oldSize), oldFileSize, &nextBoxInfo, true /* throw errors */ ); + + XMP_Uns64 totalRoom = oldSize + nextBoxInfo.headerSize + nextBoxInfo.contentSize; - // The current final box is "to-EoF", we need to write the actual size. If the size fits - // in 32 bits then we just set it in the leading Uns32 field instead of the "to-EoF" - // value. Otherwise we have to insert a 64-bit size. If the previous box is 'free' then - // we take 8 bytes from the end of it, shift the size/type parts of the final box up 8 - // bytes, making space for the 64-bit size. Give up if the previous box is not 'free'. + bool nextIsFree = (nextBoxInfo.boxType == ISOMedia::k_free) || (nextBoxInfo.boxType == ISOMedia::k_skip); + bool haveEnoughRoom = (newSize == totalRoom) || + ( (newSize < totalRoom) && ((totalRoom - newSize) >= 8) ); - if ( currSize <= 0xFFFFFFFFULL ) { + if ( nextIsFree & haveEnoughRoom ) { - // The size fits in 32 bits, reuse the leading Uns32 size. - be32Size = MakeUns32BE ( (XMP_Uns32)currSize ); - LFA_Seek ( fileRef, currPos, SEEK_SET ); - LFA_Write ( fileRef, &be32Size, 4 ); + LFA_Seek ( fileRef, oldOffset, SEEK_SET ); + LFA_Write ( fileRef, newBox, newSize ); - } else if ( prevType != kBE_free ) { + if ( newSize < totalRoom ) { + // Don't wipe, at most 7 old bytes left, it will be covered by the free header. + WriteBoxHeader ( fileRef, ISOMedia::k_free, (totalRoom - newSize) ); + } - // We need to insert a 64-bit size, but the previous box is not 'free'. - XMP_Throw ( "MPEG4_MetaHandler::PickNewLocation - Can't set box size", kXMPErr_ExternalFailure ); + } else { - } else if ( prevSize == 8 ) { + // Create a list of all top level free space, including the old space as free. Use the + // earliest space that fits. If none, append. + + FreeSpaceList spaceList; + CreateFreeSpaceList ( fileRef, oldFileSize, oldOffset, oldSize, &spaceList ); + + size_t freeSlot, limit; + for ( freeSlot = 0, limit = spaceList.size(); freeSlot < limit; ++freeSlot ) { + XMP_Uns64 freeSize = spaceList[freeSlot].size; + if ( (newSize == freeSize) || ( (newSize < freeSize) && ((freeSize - newSize) >= 8) ) ) break; + } + + if ( freeSlot == spaceList.size() ) { + + // No available free space, append the new box. + CheckFinalBox ( fileRef, abortProc, abortArg ); + LFA_Seek ( fileRef, 0, SEEK_END ); + LFA_Write ( fileRef, newBox, newSize ); + WipeBoxFree ( fileRef, oldOffset, oldSize ); - // Absorb the whole free box. - LFA_Seek ( fileRef, prevPos, SEEK_SET ); - be32Size = MakeUns32BE ( 1 ); - LFA_Write ( fileRef, &be32Size, 4 ); - LFA_Write ( fileRef, &currType, 4 ); - be64Size = MakeUns64BE ( currSize ); - LFA_Write ( fileRef, &be64Size, 8 ); - } else { + + // Use the available free space. Wipe non-overlapping parts of the old box. The old + // box is either included in the new space, or is fully disjoint. + + SpaceInfo & newSpace = spaceList[freeSlot]; + + bool oldIsDisjoint = ((oldOffset + oldSize) <= newSpace.offset) || // Old is in front. + ((newSpace.offset + newSpace.size) <= oldOffset); // Old is behind. + + XMP_Assert ( (newSize == newSpace.size) || + ( (newSize < newSpace.size) && ((newSpace.size - newSize) >= 8) ) ); + + XMP_Assert ( oldIsDisjoint || + ( (newSpace.offset <= oldOffset) && + ((oldOffset + oldSize) <= (newSpace.offset + newSpace.size)) ) /* old is included */ ); - // Trim 8 bytes off the end of the free box. + XMP_Uns64 newFreeOffset = newSpace.offset + newSize; + XMP_Uns64 newFreeSize = newSpace.size - newSize; + + LFA_Seek ( fileRef, newSpace.offset, SEEK_SET ); + LFA_Write ( fileRef, newBox, newSize ); + + if ( newFreeSize > 0 ) WriteBoxHeader ( fileRef, ISOMedia::k_free, newFreeSize ); - prevSize -= 8; - - LFA_Seek ( fileRef, prevPos, SEEK_SET ); - LFA_Read ( fileRef, &be32Size, 4, kLFA_RequireAll ); - if ( be32Size != MakeUns32BE ( 1 ) ) { - be32Size = MakeUns32BE ( (XMP_Uns32)prevSize ); - LFA_Seek ( fileRef, prevPos, SEEK_SET ); - LFA_Write ( fileRef, &be32Size, 4 ); + if ( oldIsDisjoint ) { + + WipeBoxFree ( fileRef, oldOffset, oldSize ); + } else { - be64Size = MakeUns64BE ( prevSize ); - LFA_Seek ( fileRef, (prevPos + 8), SEEK_SET ); - LFA_Write ( fileRef, &be64Size, 8 ); - } - LFA_Seek ( fileRef, (currPos - 8), SEEK_SET ); - be32Size = MakeUns32BE ( 1 ); - LFA_Write ( fileRef, &be32Size, 4 ); - LFA_Write ( fileRef, &currType, 4 ); - be64Size = MakeUns64BE ( currSize ); - LFA_Write ( fileRef, &be64Size, 8 ); + // Clear the exposed portion of the old box. + + XMP_Uns64 zeroStart = newFreeOffset + 8; + if ( newFreeSize > 0xFFFFFFFF ) zeroStart += 8; + if ( oldOffset > zeroStart ) zeroStart = oldOffset; + XMP_Uns64 zeroEnd = newFreeOffset + newFreeSize; + if ( (oldOffset + oldSize) < zeroEnd ) zeroEnd = oldOffset + oldSize; + + if ( zeroStart < zeroEnd ) { // The new box might cover the old. + XMP_Assert ( (zeroEnd - zeroStart) <= (XMP_Uns64)oldSize ); + XMP_Uns32 zeroSize = (XMP_Uns32) (zeroEnd - zeroStart); + LFA_Seek ( fileRef, zeroStart, SEEK_SET ); + for ( XMP_Uns32 ioCount = sizeof ( kZeroes ); zeroSize > 0; zeroSize -= ioCount ) { + if ( ioCount > zeroSize ) ioCount = zeroSize; + LFA_Write ( fileRef, &kZeroes[0], ioCount ); + } + } + } + } } - + } - -} // MPEG4_MetaHandler::PickNewLocation + +} // MPEG4_MetaHandler::UpdateTopLevelBox // ================================================================================================= // MPEG4_MetaHandler::UpdateFile // ============================= - -// *** There are no writebacks to legacy metadata yet. We need to resolve the questions about -// *** standard MPEG-4 metadata versus 'ilst' metadata. - -// *** The current logic for the XMP is simple. Use the existing XMP if it is big enough, next look -// *** for 'free' space before any 'mdat' boxes, finally append to the end. - -// ! MPEG-4 can be indexed with absolute offsets, only the 'moov' and XMP 'uuid' boxes can be moved! +// +// Revamp notes: +// The 'moov' subtree and possibly the XMP 'uuid' box get updated. Compose the new copy of each and +// see if it fits in existing space, incorporating adjacent 'free' boxes if necessary. If that won't +// work, look for a sufficient 'free' box anywhere in the file. As a last resort, append the new copy. +// Assume no location sensitive data within 'moov', i.e. no offsets into it. This lets it be moved +// and its children freely rearranged. void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate ) { - if ( ! this->needsUpdate ) return; + if ( ! this->needsUpdate ) { // If needsUpdate is set then at least the XMP changed. + return; + } + this->needsUpdate = false; // Make sure only called once. XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); - + LFA_FileRef fileRef = this->parent->fileRef; XMP_Uns64 fileSize = LFA_Measure ( fileRef ); - XMP_Uns32 be32Size; - - // Make sure the XMP has a current legacy digest. - std::string newDigest; - this->MakeLegacyDigest ( &newDigest ); - this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "MPEG-4", newDigest.c_str(), kXMP_DeleteExisting ); - XMP_StringLen xmpLen = (XMP_StringLen)this->xmpPacket.size(); - try { - this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength), xmpLen ); - } catch ( ... ) { - this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); - } - - #if XMP_DebugBuild // Sanity check that the XMP is where we think it is. + bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO); - if ( this->xmpBoxPos != 0 ) { - - XMP_Uns32 boxType; - XMP_Uns64 hSize, cSize; - XMP_Uns8 uuid [16]; + // Update the 'moov' subtree with exports from the XMP, but not the XMP itself (for QT files). - GetBoxInfo ( fileRef, LFA_Measure ( fileRef ), this->xmpBoxPos, &boxType, &hSize, &cSize ); - LFA_Read ( fileRef, uuid, 16, kLFA_RequireAll ); + ExportMVHDItems ( this->xmpObj, &this->moovMgr ); + ExportISOCopyrights ( this->xmpObj, &this->moovMgr ); + ExportQuickTimeItems ( this->xmpObj, &this->tradQTMgr, &this->moovMgr ); + ExportTimecodeItems ( this->xmpObj, &this->tmcdInfo, &this->tradQTMgr, &this->moovMgr ); - if ( ((this->xmpBoxPos + hSize + 16) != (XMP_Uns64)this->packetInfo.offset) || - ((cSize - 16) != (XMP_Uns64)this->packetInfo.length) || - (boxType != kBE_uuid) || (memcmp ( uuid, kBE_xmpUUID, 16 ) != 0) ) { - XMP_Throw ( "Inaccurate MPEG-4 packet info", kXMPErr_InternalFailure ); - } - - } + if ( ! haveISOFile ) ExportCr8rItems ( this->xmpObj, &this->moovMgr ); + + // Try to update the XMP in-place if that is all that changed, or if it is in a preferred 'uuid' box. + // The XMP has already been serialized by common code to the appropriate length. Otherwise, update + // the 'moov'/'udta'/'XMP_' box in the MOOV_Manager, or the 'uuid' XMP box in the file. - #endif + bool useUuidXMP = (this->fileMode == MOOV_Manager::kFileIsNormalISO); - if ( (this->xmpBoxPos != 0) && (this->xmpPacket.size() <= (size_t)this->packetInfo.length) ) { + if ( (this->xmpPacket.size() == (size_t)this->packetInfo.length) && + ( (useUuidXMP & this->havePreferredXMP) || (! this->moovMgr.IsChanged()) ) ) { - // Update existing XMP in-place. - - if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { - // They ought to match, cheap to be sure. - size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); - this->xmpPacket.append ( extraSpace, ' ' ); - } - - XMP_Assert ( this->xmpPacket.size() == (size_t)this->packetInfo.length ); + // Update the existing XMP in-place. LFA_Seek ( fileRef, this->packetInfo.offset, SEEK_SET ); - LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() ); + LFA_Write ( fileRef, this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() ); - } else if ( (this->xmpBoxPos != 0) && - ((XMP_Uns64)(this->packetInfo.offset + this->packetInfo.length) == fileSize) ) { + } else if ( ! useUuidXMP ) { - // The XMP is already at the end of the file, rewrite the whole 'uuid' box. - - XMP_Assert ( this->xmpPacket.size() > (size_t)this->packetInfo.length ); - - LFA_Seek ( fileRef, this->xmpBoxPos, SEEK_SET ); - be32Size = MakeUns32BE ( 4+4+16 + (XMP_Uns32)this->xmpPacket.size() ); // ! XMP must be 4GB or less! - LFA_Write ( fileRef, &be32Size, 4 ); - LFA_Write ( fileRef, &kBE_uuid, 4 ); - LFA_Write ( fileRef, &kBE_xmpUUID, 16 ); - LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() ); + // Don't leave an old uuid XMP around (if we know about it). + if ( (! havePreferredXMP) && (this->xmpBoxSize != 0) ) { + WipeBoxFree ( fileRef, this->xmpBoxPos, this->xmpBoxSize ); + } + + // The udta form of XMP has just the XMP packet. + this->moovMgr.SetBox ( "moov/udta/XMP_", this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() ); } else { - - // We are injecting first time XMP, or making the existing XMP larger. Pick a new location - // for the XMP. This is the first available space before any 'mdat' box, otherwise the end - // of the file. Available space can be any contiguous combination of 'free' space with the - // existing XMP. Mark any existing XMP as 'free' first, this simplifies the logic and we - // can't do it later since we might reuse the space. - - if ( this->xmpBoxPos != 0 ) { - LFA_Seek ( fileRef, (this->xmpBoxPos + 4), SEEK_SET ); - LFA_Write ( fileRef, "free", 4 ); - } + + // Don't leave an old 'moov'/'udta'/'XMP_' box around. + MOOV_Manager::BoxRef udtaRef = this->moovMgr.GetBox ( "moov/udta", 0 ); + if ( udtaRef != 0 ) this->moovMgr.DeleteTypeChild ( udtaRef, ISOMedia::k_XMP_ ); - this->PickNewLocation(); // ! Might increase the size of the XMP packet. + // The uuid form of XMP has the 16-byte UUID in front of the XMP packet. Form the complete + // box (including size/type header) for UpdateTopLevelBox. + RawDataBlock uuidBox; + XMP_Uns32 uuidSize = 4+4 + 16 + (XMP_Uns32)this->xmpPacket.size(); + uuidBox.assign ( uuidSize, 0 ); + PutUns32BE ( uuidSize, &uuidBox[0] ); + PutUns32BE ( ISOMedia::k_uuid, &uuidBox[4] ); + memcpy ( &uuidBox[8], ISOMedia::k_xmpUUID, 16 ); + memcpy ( &uuidBox[24], this->xmpPacket.c_str(), this->xmpPacket.size() ); + this->UpdateTopLevelBox ( this->xmpBoxPos, this->xmpBoxSize, &uuidBox[0], uuidSize ); - LFA_Seek ( fileRef, this->xmpBoxPos, SEEK_SET ); - be32Size = MakeUns32BE ( 4+4+16 + (XMP_Uns32)this->xmpPacket.size() ); // ! XMP must be 4GB or less! - LFA_Write ( fileRef, &be32Size, 4 ); - LFA_Write ( fileRef, &kBE_uuid, 4 ); - LFA_Write ( fileRef, &kBE_xmpUUID, 16 ); - LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() ); + } + + // Update the 'moov' subtree if necessary, and finally update the timecode sample. + if ( this->moovMgr.IsChanged() ) { + this->moovMgr.UpdateMemoryTree(); + this->UpdateTopLevelBox ( moovBoxPos, moovBoxSize, &this->moovMgr.fullSubtree[0], (XMP_Uns32)this->moovMgr.fullSubtree.size() ); } + if ( this->tmcdInfo.sampleOffset != 0 ) { + LFA_Seek ( fileRef, this->tmcdInfo.sampleOffset, SEEK_SET ); + XMP_Uns32 sample = MakeUns32BE ( this->tmcdInfo.timecodeSample ); + LFA_Write ( fileRef, &sample, 4 ); + } + } // MPEG4_MetaHandler::UpdateFile // ================================================================================================= @@ -894,14 +2441,14 @@ void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate ) void MPEG4_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) { XMP_Assert ( this->needsUpdate ); - + LFA_FileRef destRef = this->parent->fileRef; - + LFA_Seek ( sourceRef, 0, SEEK_SET ); LFA_Seek ( destRef, 0, SEEK_SET ); LFA_Copy ( sourceRef, destRef, LFA_Measure ( sourceRef ), this->parent->abortProc, this->parent->abortArg ); - + this->UpdateFile ( false ); } // MPEG4_MetaHandler::WriteFile diff --git a/source/XMPFiles/FileHandlers/MPEG4_Handler.hpp b/source/XMPFiles/FileHandlers/MPEG4_Handler.hpp index 9da2741..9d00781 100644 --- a/source/XMPFiles/FileHandlers/MPEG4_Handler.hpp +++ b/source/XMPFiles/FileHandlers/MPEG4_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -12,6 +12,9 @@ #include "XMPFiles_Impl.hpp" +#include "MOOV_Support.hpp" +#include "QuickTime_Support.hpp" + // ================================================================================================ /// \file MPEG4_Handler.hpp /// \brief File format handler for MPEG-4. @@ -23,9 +26,9 @@ extern XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent ); extern bool MPEG4_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef fileRef, - XMPFiles * parent ); + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ); static const XMP_OptionBits kMPEG4_HandlerFlags = ( kXMPFiles_CanInjectXMP | kXMPFiles_CanExpand | @@ -43,24 +46,46 @@ public: void CacheFileData(); void ProcessXMP(); - + void UpdateFile ( bool doSafeUpdate ); void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); MPEG4_MetaHandler ( XMPFiles * _parent ); virtual ~MPEG4_MetaHandler(); + struct TimecodeTrackInfo { // Info about a QuickTime timecode track. + bool stsdBoxFound, isDropFrame; + XMP_Uns32 timeScale; + XMP_Uns32 frameDuration; + XMP_Uns32 timecodeSample; + XMP_Uns64 sampleOffset; // Absolute file offset of the timecode sample, 0 if none. + XMP_Uns32 nameOffset; // The offset of the 'name' box relative to the 'stsd' box content. + XMP_Uns16 macLang; // The Mac language code of the trailing 'name' box. + std::string macName; // The text part of the trailing 'name' box, in macLang encoding. + TimecodeTrackInfo() + : stsdBoxFound(false), isDropFrame(false), timeScale(0), frameDuration(0), + timecodeSample(0), sampleOffset(0), nameOffset(0), macLang(0) {}; + }; + private: - MPEG4_MetaHandler() : xmpBoxPos(0) {}; // Hidden on purpose. + MPEG4_MetaHandler() : fileMode(0), havePreferredXMP(false), + xmpBoxPos(0), moovBoxPos(0), xmpBoxSize(0), moovBoxSize(0) {}; // Hidden on purpose. + + bool ParseTimecodeTrack(); - void MakeLegacyDigest ( std::string * digestStr ); - void PickNewLocation(); + void UpdateTopLevelBox ( XMP_Uns64 oldOffset, XMP_Uns32 oldSize, const XMP_Uns8 * newBox, XMP_Uns32 newSize ); + XMP_Uns8 fileMode; + bool havePreferredXMP; XMP_Uns64 xmpBoxPos; // The file offset of the XMP box (the size field, not the content). + XMP_Uns64 moovBoxPos; // The file offset of the 'moov' box (the size field, not the content). + XMP_Uns32 xmpBoxSize, moovBoxSize; // The full size of the boxes, not just the content. + + MOOV_Manager moovMgr; + TradQT_Manager tradQTMgr; - std::string mvhdBox; // ! Both contain binary data, but std::string is handy. - std::vector<std::string> cprtBoxes; + TimecodeTrackInfo tmcdInfo; }; // MPEG4_MetaHandler diff --git a/source/XMPFiles/FileHandlers/P2_Handler.cpp b/source/XMPFiles/FileHandlers/P2_Handler.cpp index eac0edf..5e3af85 100644 --- a/source/XMPFiles/FileHandlers/P2_Handler.cpp +++ b/source/XMPFiles/FileHandlers/P2_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -10,6 +10,7 @@ #include "P2_Handler.hpp" #include "MD5.h" +#include <cmath> using namespace std; @@ -184,9 +185,9 @@ bool P2_CheckFormat ( XMP_FileFormat format, tempPath += clipName; size_t pathLen = tempPath.size() + 1; // Include a terminating nul. - parent->handlerTemp = malloc ( pathLen ); - if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for P2 clip path", kXMPErr_NoMemory ); - memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. + parent->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for P2 clip path", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. return true; @@ -213,12 +214,12 @@ P2_MetaHandler::P2_MetaHandler ( XMPFiles * _parent ) : expat(0), clipMetadata(0 this->handlerFlags = kP2_HandlerFlags; this->stdCharForm = kXMP_Char8Bit; - // Extract the root path and clip name from handlerTemp. + // Extract the root path and clip name from tempPtr. - XMP_Assert ( this->parent->handlerTemp != 0 ); - this->rootPath = (char*)this->parent->handlerTemp; - free ( this->parent->handlerTemp ); - this->parent->handlerTemp = 0; + XMP_Assert ( this->parent->tempPtr != 0 ); + this->rootPath = (char*)this->parent->tempPtr; + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; SplitLeafName ( &this->rootPath, &this->clipName ); @@ -232,9 +233,9 @@ P2_MetaHandler::~P2_MetaHandler() { this->CleanupLegacyXML(); - if ( this->parent->handlerTemp != 0 ) { - free ( this->parent->handlerTemp ); - this->parent->handlerTemp = 0; + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; } } // P2_MetaHandler::~P2_MetaHandler @@ -256,11 +257,6 @@ void P2_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix void P2_MetaHandler::CleanupLegacyXML() { - - if ( ! this->defaultNS.empty() ) { - SXMPMeta::DeleteNamespace ( this->defaultNS.c_str() ); - this->defaultNS.erase(); - } if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; } @@ -568,16 +564,42 @@ void P2_MetaHandler::SetVideoFrameInfoFromLegacyXML ( XML_NodePtr legacyVideoCon dmHeight = "720"; dmWidth = "960"; dmPixelAspectRatio = "1920/1440"; - } else if ( ( p2Codec == "AVC-I_1080/59.94i" ) || - ( p2Codec == "AVC-I_1080/50i" ) || - ( p2Codec == "AVC-I_1080/29.97p" ) || - ( p2Codec == "AVC-I_1080/25p" ) || - ( p2Codec == "AVC-I_720/59.94p" ) || - ( p2Codec == "AVC-I_720/50p" ) ) { - // There are two "flavors" of the AVC-Intra codec that compress to different widths (and, therefore - // different pixel aspect ratios), but no way I can find to distinguish between them in the - // legacy XML. Until this is resolved we'll just report the codec name. - dmVideoCompressor = "AVC-Intra"; + } else if ( ( p2Codec.compare ( 0, 6, "AVC-I_" ) == 0 ) ) { + + // This is AVC-Intra footage. The framerate and PAR depend on the "class" attribute in the P2 XML. + const XMP_StringPtr codecClass = legacyProp->GetAttrValue( "Class" ); + + if ( XMP_LitMatch ( codecClass, "100" ) ) { + + dmVideoCompressor = "AVC-Intra 100"; + dmPixelAspectRatio = "1/1"; + + if ( p2Codec.compare ( 6, 4, "1080" ) == 0 ) { + dmHeight = "1080"; + dmWidth = "1920"; + } else if ( p2Codec.compare ( 6, 3, "720" ) == 0 ) { + dmHeight = "720"; + dmWidth = "1280"; + } + + } else if ( XMP_LitMatch ( codecClass, "50" ) ) { + + dmVideoCompressor = "AVC-Intra 50"; + dmPixelAspectRatio = "1920/1440"; + + if ( p2Codec.compare ( 6, 4, "1080" ) == 0 ) { + dmHeight = "1080"; + dmWidth = "1440"; + } else if ( p2Codec.compare ( 6, 3, "720" ) == 0 ) { + dmHeight = "720"; + dmWidth = "960"; + } + + } else { + // Unknown codec class -- we don't have enough info to determine the + // codec, PAR, or aspect ratio + dmVideoCompressor = "AVC-Intra"; + } } if ( dmWidth == "720" ) { @@ -662,7 +684,8 @@ void P2_MetaHandler::SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoCont if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { const std::string p2FrameRate = legacyProp->GetLeafContentValue(); - const XMP_StringPtr p2DropFrameFlag = legacyProp->GetAttrValue ( "DropFrameFlag" ); + XMP_StringPtr p2DropFrameFlag = legacyProp->GetAttrValue ( "DropFrameFlag" ); + if ( p2DropFrameFlag == 0 ) p2DropFrameFlag = ""; // Make tests easier. std::string dmTimeFormat; if ( ( p2FrameRate == "50i" ) || ( p2FrameRate == "25p" ) ) { @@ -679,21 +702,21 @@ void P2_MetaHandler::SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoCont } else if ( p2FrameRate == "59.94p" ) { - if ( p2DropFrameFlag == "true" ) { + if ( XMP_LitMatch ( p2DropFrameFlag, "true" ) ) { dmTimeFormat = "5994DropTimecode"; - } else if ( p2DropFrameFlag == "false" ) { + } else if ( XMP_LitMatch ( p2DropFrameFlag, "false" ) ) { dmTimeFormat = "5994NonDropTimecode"; } - } else if ( ( p2FrameRate == "59.94i" ) || ( p2FrameRate == "29.97p" ) ) { + } else if ( (p2FrameRate == "59.94i") || (p2FrameRate == "29.97p") ) { if ( p2DropFrameFlag != 0 ) { - if ( std::strcmp ( p2DropFrameFlag, "false" ) == 0 ) { + if ( XMP_LitMatch ( p2DropFrameFlag, "false" ) ) { dmTimeFormat = "2997NonDropTimecode"; - } else if ( std::strcmp ( p2DropFrameFlag, "true" ) == 0 ) { + } else if ( XMP_LitMatch ( p2DropFrameFlag, "true" ) ) { // Drop frame NTSC timecode uses semicolons instead of colons as separators. std::string::iterator currCharIt = p2StartTimecode.begin(); @@ -725,6 +748,101 @@ void P2_MetaHandler::SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoCont } // P2_MetaHandler::SetStartTimecodeFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetGPSPropertyFromLegacyXML +// =========================================== + +void P2_MetaHandler::SetGPSPropertyFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound, XMP_StringPtr propName, XMP_StringPtr legacyPropName ) +{ + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_EXIF, propName )) ) { + + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyGPSProp = legacyLocationContext->GetNamedElement ( p2NS, legacyPropName ); + + if ( ( legacyGPSProp != 0 ) && legacyGPSProp->IsLeafContentNode() ) { + + this->xmpObj.DeleteProperty ( kXMP_NS_EXIF, propName ); + + const std::string legacyGPSValue = legacyGPSProp->GetLeafContentValue(); + + if ( ! legacyGPSValue.empty() ) { + + // Convert from decimal to sexagesimal GPS coordinates + char direction = '\0'; + double degrees = 0.0; + const int numFieldsRead = sscanf ( legacyGPSValue.c_str(), "%c%lf", &direction, °rees ); + + if ( numFieldsRead == 2 ) { + double wholeDegrees = 0.0; + const double fractionalDegrees = modf ( degrees, &wholeDegrees ); + const double minutes = fractionalDegrees * 60.0; + char xmpValue [128]; + + sprintf ( xmpValue, "%d,%.5lf%c", static_cast<int>(wholeDegrees), minutes, direction ); + this->xmpObj.SetProperty ( kXMP_NS_EXIF, propName, xmpValue ); + this->containsXMP = true; + + } + + } + + } + + } + +} // P2_MetaHandler::SetGPSPropertyFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetAltitudeFromLegacyXML +// ======================================== + +void P2_MetaHandler::SetAltitudeFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound ) +{ + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_EXIF, "GPSAltitude" )) ) { + + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyAltitudeProp = legacyLocationContext->GetNamedElement ( p2NS, "Altitude" ); + + if ( ( legacyAltitudeProp != 0 ) && legacyAltitudeProp->IsLeafContentNode() ) { + + this->xmpObj.DeleteProperty ( kXMP_NS_EXIF, "GPSAltitude" ); + + const std::string legacyGPSValue = legacyAltitudeProp->GetLeafContentValue(); + + if ( ! legacyGPSValue.empty() ) { + + int altitude = 0; + + if ( sscanf ( legacyGPSValue.c_str(), "%d", &altitude ) == 1) { + + if ( altitude >= 0 ) { + // At or above sea level. + this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "0" ); + } else { + // Below sea level. + altitude = -altitude; + this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "1" ); + } + + char xmpValue [128]; + + sprintf ( xmpValue, "%d/1", altitude ); + this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitude", xmpValue ); + this->containsXMP = true; + + } + + } + + } + + } + +} // P2_MetaHandler::SetAltitudeFromLegacyXML + // ================================================================================================= // P2_MetaHandler::ForceChildElement // ================================= @@ -813,12 +931,44 @@ void P2_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) this->DigestLegacyItem ( md5Context, legacyContext, "ShotMark" ); legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Access" ); + /* Rather return than create the digest because the "Access" element is listed as "required" in the P2 spec. + So a P2 file without an "Access" element does not follow the spec and might be corrupt.*/ if ( legacyContext == 0 ) return; this->DigestLegacyItem ( md5Context, legacyContext, "Creator" ); this->DigestLegacyItem ( md5Context, legacyContext, "CreationDate" ); this->DigestLegacyItem ( md5Context, legacyContext, "LastUpdateDate" ); + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Shoot" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "Shooter" ); + + legacyContext = legacyContext->GetNamedElement ( p2NS, "Location" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "PlaceName" ); + this->DigestLegacyItem ( md5Context, legacyContext, "Longitude" ); + this->DigestLegacyItem ( md5Context, legacyContext, "Latitude" ); + this->DigestLegacyItem ( md5Context, legacyContext, "Altitude" ); + } + } + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Scenario" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "SceneNo." ); + this->DigestLegacyItem ( md5Context, legacyContext, "TakeNo." ); + } + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Device" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "Manufacturer" ); + this->DigestLegacyItem ( md5Context, legacyContext, "SerialNo." ); + this->DigestLegacyItem ( md5Context, legacyContext, "ModelName" ); + } + MD5Final ( digestBin, &md5Context ); char buffer [40]; @@ -838,7 +988,7 @@ void P2_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) void P2_MetaHandler::CacheFileData() { - XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + XMP_Assert ( ! this->containsXMP ); // Make sure the clip's .XMP file exists. @@ -906,12 +1056,6 @@ void P2_MetaHandler::ProcessXMP() this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); } - // -------------------------------------------------------------- - // *** This is a minimal Q&D example of legacy metadata handling. - // *** Hack: Special case trickery to detect and clean up default XML namespace usage. - - bool haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", 0 ); // Is there already a default namespace? - std::string xmlPath; this->MakeClipFilePath ( &xmlPath, ".XML" ); @@ -919,7 +1063,7 @@ void P2_MetaHandler::ProcessXMP() xmlFile.fileRef = LFA_Open ( xmlPath.c_str(), 'r' ); if ( xmlFile.fileRef == 0 ) return; // The open failed. - this->expat = XMP_NewExpatAdapter(); + this->expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); if ( this->expat == 0 ) XMP_Throw ( "P2_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory ); XMP_Uns8 buffer [64*1024]; @@ -933,12 +1077,6 @@ void P2_MetaHandler::ProcessXMP() LFA_Close ( xmlFile.fileRef ); xmlFile.fileRef = 0; - if ( ! haveDefaultNS ) { - // No prior default XML namespace. If there is one now, remember it and delete it when done. - haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", &this->defaultNS ); - XMP_Assert ( haveDefaultNS == (! this->defaultNS.empty()) ); - } - // The root element should be P2Main in some namespace. At least 2 different namespaces are in // use (ending in "v3.0" and "v3.1"). Take whatever this file uses. @@ -997,7 +1135,7 @@ void P2_MetaHandler::ProcessXMP() legacyProp = legacyContext->GetNamedElement ( p2NS, "Creator" ); if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { this->xmpObj.DeleteProperty ( kXMP_NS_DC, "creator" ); - this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsUnordered, + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, legacyProp->GetLeafContentValue() ); this->containsXMP = true; } @@ -1006,26 +1144,48 @@ void P2_MetaHandler::ProcessXMP() this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_XMP, "CreateDate", "CreationDate", false ); this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_XMP, "ModifyDate", "LastUpdateDate", false ); - if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "Rating" )) ) { - legacyProp = legacyContext->GetNamedElement ( p2NS, "ShotMark" ); - if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "good" )) ) { + legacyProp = this->clipMetadata->GetNamedElement ( p2NS, "ShotMark" ); + if ( (legacyProp == 0) || (! legacyProp->IsLeafContentNode()) ) { + this->xmpObj.DeleteProperty ( kXMP_NS_DM, "good" ); + } else { XMP_StringPtr markValue = legacyProp->GetLeafContentValue(); - - // Translate "marked" clips as having a rating of 1 and "unmarked" clips as having a rating of 0 - if ((markValue == 0) || (strcmp(markValue, "false") == 0) || (strcmp(markValue, "0") == 0)) { - this->xmpObj.SetProperty ( kXMP_NS_XMP, "Rating", "0", kXMP_DeleteExisting ); + if ( markValue == 0 ) { + this->xmpObj.DeleteProperty ( kXMP_NS_DM, "good" ); + } else if ( XMP_LitMatch ( markValue, "true" ) || XMP_LitMatch ( markValue, "1" ) ) { + this->xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", true, kXMP_DeleteExisting ); this->containsXMP = true; - } else if ((strcmp(markValue, "true") == 0) || (strcmp(markValue, "1") == 0)) { - this->xmpObj.SetProperty ( kXMP_NS_XMP, "Rating", "1", kXMP_DeleteExisting ); + } else if ( XMP_LitMatch ( markValue, "false" ) || XMP_LitMatch ( markValue, "0" ) ) { + this->xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", false, kXMP_DeleteExisting ); this->containsXMP = true; } } } - legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Location" ); + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Shoot" ); + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Artist", "Shooter", false ); + legacyContext = legacyContext->GetNamedElement ( p2NS, "Location" ); + } if ( legacyContext != 0 ) { this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "shotLocation", "PlaceName", false ); + this->SetGPSPropertyFromLegacyXML ( legacyContext, digestFound, "GPSLongitude", "Longitude" ); + this->SetGPSPropertyFromLegacyXML ( legacyContext, digestFound, "GPSLatitude", "Latitude" ); + this->SetAltitudeFromLegacyXML ( legacyContext, digestFound ); + } + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Device" ); + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Make", "Manufacturer", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_EXIF_Aux, "SerialNumber", "SerialNo.", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Model", "ModelName", false ); + } + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Scenario" ); + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "scene", "SceneNo.", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "takeNumber", "TakeNo.", false ); } CleanupAndExit diff --git a/source/XMPFiles/FileHandlers/P2_Handler.hpp b/source/XMPFiles/FileHandlers/P2_Handler.hpp index 8581fbd..3d0cd96 100644 --- a/source/XMPFiles/FileHandlers/P2_Handler.hpp +++ b/source/XMPFiles/FileHandlers/P2_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -90,10 +90,12 @@ private: void SetVideoFrameInfoFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound ); void SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound ); + void SetGPSPropertyFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound, XMP_StringPtr propName, XMP_StringPtr legacyPropName ); + void SetAltitudeFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound ); XML_Node * ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, int indent = 0 ); - std::string rootPath, clipName, p2NS, defaultNS; + std::string rootPath, clipName, p2NS; ExpatAdapter * expat; XML_Node * clipMetadata; // ! Don't delete, points into the Expat tree. diff --git a/source/XMPFiles/FileHandlers/PNG_Handler.cpp b/source/XMPFiles/FileHandlers/PNG_Handler.cpp index 7e82eaa..0185fce 100644 --- a/source/XMPFiles/FileHandlers/PNG_Handler.cpp +++ b/source/XMPFiles/FileHandlers/PNG_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -112,17 +112,6 @@ void PNG_MetaHandler::CacheFileData() } // PNG_MetaHandler::CacheFileData // ================================================================================================= -// PNG_MetaHandler::ProcessTNail -// ============================== - -void PNG_MetaHandler::ProcessTNail() -{ - - XMP_Throw ( "PNG_MetaHandler::ProcessTNail isn't implemented yet", kXMPErr_Unimplemented ); - -} // PNG_MetaHandler::ProcessTNail - -// ================================================================================================= // PNG_MetaHandler::ProcessXMP // ============================ // @@ -260,6 +249,7 @@ bool PNG_MetaHandler::SafeWriteFile () ret = true; } catch ( ... ) { LFA_Close ( updateRef ); + LFA_Delete ( updatePath.c_str() ); this->parent->filePath = origPath; this->parent->fileRef = origRef; throw; diff --git a/source/XMPFiles/FileHandlers/PNG_Handler.hpp b/source/XMPFiles/FileHandlers/PNG_Handler.hpp index 92ee15c..cc978c3 100644 --- a/source/XMPFiles/FileHandlers/PNG_Handler.hpp +++ b/source/XMPFiles/FileHandlers/PNG_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -25,24 +25,22 @@ extern XMPFileHandler* PNG_MetaHandlerCTor ( XMPFiles* parent ); extern bool PNG_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef fileRef, - XMPFiles* parent ); + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ); static const XMP_OptionBits kPNG_HandlerFlags = ( kXMPFiles_CanInjectXMP | kXMPFiles_CanExpand | kXMPFiles_PrefersInPlace | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | - kXMPFiles_NeedsReadOnlyPacket - ); + kXMPFiles_NeedsReadOnlyPacket ); class PNG_MetaHandler : public XMPFileHandler { public: void CacheFileData(); - void ProcessTNail(); void ProcessXMP(); void UpdateFile ( bool doSafeUpdate ); diff --git a/source/XMPFiles/FileHandlers/PSD_Handler.cpp b/source/XMPFiles/FileHandlers/PSD_Handler.cpp index 600be29..b81ef0c 100644 --- a/source/XMPFiles/FileHandlers/PSD_Handler.cpp +++ b/source/XMPFiles/FileHandlers/PSD_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -13,6 +13,7 @@ #include "PSIR_Support.hpp" #include "IPTC_Support.hpp" #include "ReconcileLegacy.hpp" +#include "Reconcile_Impl.hpp" #include "MD5.h" @@ -47,17 +48,17 @@ bool PSD_CheckFormat ( XMP_FileFormat format, XMP_Assert ( format == kXMP_PhotoshopFile ); IOBuffer ioBuf; - + LFA_Seek ( fileRef, 0, SEEK_SET ); if ( ! CheckFileSpace ( fileRef, &ioBuf, 34 ) ) return false; // 34 = header plus 2 lengths - + if ( ! CheckBytes ( ioBuf.ptr, "8BPS", 4 ) ) return false; ioBuf.ptr += 4; // Move to the version. XMP_Uns16 version = GetUns16BE ( ioBuf.ptr ); if ( (version != 1) && (version != 2) ) return false; return true; - + } // PSD_CheckFormat // ================================================================================================= @@ -107,18 +108,18 @@ void PSD_MetaHandler::CacheFileData() { LFA_FileRef fileRef = this->parent->fileRef; XMP_PacketInfo & packetInfo = this->packetInfo; - + XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); - - XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + + XMP_Assert ( ! this->containsXMP ); // Set containsXMP to true here only if the XMP image resource is found. - + if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "PSD_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); } - + XMP_Uns8 psdHeader[30]; XMP_Int64 filePos; XMP_Uns32 ioLen, cmLen, psirLen; @@ -127,12 +128,12 @@ void PSD_MetaHandler::CacheFileData() ioLen = LFA_Read ( fileRef, psdHeader, 30 ); if ( ioLen != 30 ) return; // Throw? - + this->imageHeight = GetUns32BE ( &psdHeader[14] ); this->imageWidth = GetUns32BE ( &psdHeader[18] ); - + cmLen = GetUns32BE ( &psdHeader[26] ); - + XMP_Int64 psirOrigin = 26 + 4 + cmLen; filePos = LFA_Seek ( fileRef, psirOrigin, SEEK_SET ); @@ -142,12 +143,12 @@ void PSD_MetaHandler::CacheFileData() if ( ioLen != 4 ) return; // Throw? psirLen = GetUns32BE ( &psdHeader[0] ); - + this->psirMgr.ParseFileResources ( fileRef, psirLen ); - + PSIR_Manager::ImgRsrcInfo xmpInfo; bool found = this->psirMgr.GetImgRsrc ( kPSIR_XMP, &xmpInfo ); - + if ( found ) { // printf ( "PSD_MetaHandler::CacheFileData - XMP packet offset %d (0x%X), size %d\n", @@ -163,58 +164,8 @@ void PSD_MetaHandler::CacheFileData() this->containsXMP = true; } - -} // PSD_MetaHandler::CacheFileData - -// ================================================================================================= -// PSD_MetaHandler::ProcessTNail -// ============================= -// -// A Photoshop file has a thumbnail in image resource 1036. This has the following layout: -// -// offset length description -// 0 4 Format: 0 = kRawRGB, 1 = kJpegRGB -// 4 4 Thumbnail image width in pixels -// 8 4 Thumbnail image height in pixels -// 12 4 Width bytes: Padded row bytes = (width * bits per pixel + 31) / 32 * 4 -// 16 4 Total size = widthbytes * height * planes -// 20 4 Size after compression, used for consistency check -// 24 2 Bits per pixel, must be 24 -// 26 2 Number of planes, must be 1 -// 28 - Thumbnail image stream -// -// We return the full image resource as the tnailImage part of the XMP_ThumbnailInfo. The client is -// responsible for all further parsing, including conversions on little endian machines. (Since -// Photoshop image resources are always stored big endian.) -// -// Actually image resource 1036 is for Photoshop 5 and later. Photoshop 4 used image resource 1033, -// which was similar but used BGR pixels. We are ignoring the earlier format. - -void PSD_MetaHandler::ProcessTNail() -{ - this->processedTNail = true; // Make sure we only come through here once. - this->containsTNail = false; // Set it to true after all of the info is gathered. - - PSIR_Manager::ImgRsrcInfo tnailRsrc; - bool found = this->psirMgr.GetImgRsrc ( kPSIR_Thumbnail, &tnailRsrc ); - if ( ! found ) return; - - this->tnailInfo.fileFormat = this->parent->format; - this->tnailInfo.tnailFormat = kXMP_PShopTNail; - - this->tnailInfo.fullWidth = this->imageWidth; - this->tnailInfo.fullHeight = this->imageHeight; - - this->tnailInfo.tnailImage = (XMP_Uns8*)tnailRsrc.dataPtr; - this->tnailInfo.tnailSize = tnailRsrc.dataLen; - this->tnailInfo.tnailWidth = GetUns32BE ( this->tnailInfo.tnailImage + 4 ); - this->tnailInfo.tnailHeight = GetUns32BE ( this->tnailInfo.tnailImage + 8 ); - this->tnailInfo.fullOrientation = this->tnailInfo.tnailOrientation = 0; // ! Not present in PSD files. - - this->containsTNail = true; - -} // PSD_MetaHandler::ProcessTNail +} // PSD_MetaHandler::CacheFileData // ================================================================================================= // PSD_MetaHandler::ProcessXMP @@ -224,37 +175,19 @@ void PSD_MetaHandler::ProcessTNail() void PSD_MetaHandler::ProcessXMP() { - + this->processedXMP = true; // Make sure we only come through here once. // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy // import if the XMP packet gets parsing errors. - - // Parse the IPTC and Exif, determine the last-legacy priority. For PSD files the relevant - // legacy priorities (ignoring Mac pnot and ANPA resources) are: - // kLegacyJTP_PSIR_OldCaption - highest - // kLegacyJTP_PSIR_IPTC - // kLegacyJTP_None - lowest - - // ! Yes, TIFF tags 270 (Image Description, dc:description), 315 (Artist, dc:creator), and - // ! 33432 (Copyright, dc:rights) are ignored in Photoshop files. The only imported legacy forms - // ! of these are IPTC DataSets 2:120 (Caption), 2:80 (By-line), and 2:116 ( Copyright Notice). - - bool found; - RecJTP_LegacyPriority lastLegacy = kLegacyJTP_None; - + bool readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0); if ( readOnly ) { this->iptcMgr = new IPTC_Reader(); this->exifMgr = new TIFF_MemoryReader(); } else { - #if ! XMP_UNIXBuild - this->iptcMgr = new IPTC_Writer(); // ! Parse it later. - #else - // ! Hack until the legacy-as-local issues are resolved for generic UNIX. - this->iptcMgr = new IPTC_Reader(); // ! Import IPTC but don't export it. - #endif + this->iptcMgr = new IPTC_Writer(); // ! Parse it later. this->exifMgr = new TIFF_FileWriter(); } @@ -265,20 +198,25 @@ void PSD_MetaHandler::ProcessXMP() PSIR_Manager::ImgRsrcInfo iptcInfo, exifInfo; bool haveIPTC = psir.GetImgRsrc ( kPSIR_IPTC, &iptcInfo ); bool haveExif = psir.GetImgRsrc ( kPSIR_Exif, &exifInfo ); + int iptcDigestState = kDigestMatches; - found = psir.GetImgRsrc ( kPSIR_OldCaption, 0 ); - if ( ! found ) found = psir.GetImgRsrc ( kPSIR_OldCaptionPStr, 0 ); - if ( found ) lastLegacy = kLegacyJTP_PSIR_OldCaption; + if ( haveExif ) exif.ParseMemoryStream ( exifInfo.dataPtr, exifInfo.dataLen ); if ( haveIPTC ) { - iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); - if ( lastLegacy < kLegacyJTP_PSIR_IPTC ) lastLegacy = kLegacyJTP_PSIR_IPTC; - } - - if ( haveExif ) { - exif.ParseMemoryStream ( exifInfo.dataPtr, exifInfo.dataLen ); + + bool haveDigest = false; + PSIR_Manager::ImgRsrcInfo digestInfo; + haveDigest = psir.GetImgRsrc ( kPSIR_IPTCDigest, &digestInfo ); + if ( digestInfo.dataLen != 16 ) haveDigest = false; + + if ( ! haveDigest ) { + iptcDigestState = kDigestMissing; + } else { + iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, iptcInfo.dataLen, digestInfo.dataPtr ); + } + } - + XMP_OptionBits options = 0; if ( this->containsXMP ) options |= k2XMP_FileHadXMP; if ( haveIPTC ) options |= k2XMP_FileHadIPTC; @@ -288,6 +226,8 @@ void PSD_MetaHandler::ProcessXMP() // exception. This tells the caller that an error happened, but gives them recovered legacy // should they want to proceed with that. + bool haveXMP = false; + if ( ! this->xmpPacket.empty() ) { XMP_Assert ( this->containsXMP ); // Common code takes care of packetInfo.charForm, .padSize, and .writeable. @@ -295,18 +235,24 @@ 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 ); - ImportJTPtoXMP ( kXMP_JPEGFile, lastLegacy, &exif, psir, &iptc, &this->xmpObj, options ); + if ( haveIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); + if ( iptcDigestState == kDigestMatches ) iptcDigestState = kDigestMissing; + ImportPhotoData ( exif, iptc, psir, iptcDigestState, &this->xmpObj, options ); throw; // ! Rethrow the exception, don't absorb it. } } // Process the legacy metadata. - ImportJTPtoXMP ( kXMP_PhotoshopFile, lastLegacy, &exif, psir, &iptc, &this->xmpObj, options ); + if ( haveIPTC && (! haveXMP) && (iptcDigestState == kDigestMatches) ) iptcDigestState = kDigestMissing; + bool parseIPTC = (iptcDigestState != kDigestMatches) || (! readOnly); + if ( parseIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); + ImportPhotoData ( exif, iptc, psir, iptcDigestState, &this->xmpObj, options ); this->containsXMP = true; // Assume we now have something in the XMP. - + } // PSD_MetaHandler::ProcessXMP // ================================================================================================= @@ -317,22 +263,33 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) { XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. - // Decide whether to do an in-place update. This can only happen if all of the following are true: - // - There is an XMP packet in the file. - // - The are no changes to the legacy image resources. (The IPTC and EXIF are in the PSIR.) - // - The new XMP can fit in the old space. - - ExportXMPtoJTP ( kXMP_PhotoshopFile, &this->xmpObj, this->exifMgr, &this->psirMgr, this->iptcMgr ); - XMP_Int64 oldPacketOffset = this->packetInfo.offset; XMP_Int32 oldPacketLength = this->packetInfo.length; - // printf ( "PSD_MetaHandler::UpdateFile - XMP old packet offset %lld (0x%llX), size %d\n", - // oldPacketOffset, oldPacketOffset, oldPacketLength ); - + if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; + + bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0)); + + // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and + // exif: copies from the XMP, so reserialize the now final XMP packet. - bool doInPlace = (this->xmpPacket.size() <= (size_t)this->packetInfo.length); + ExportPhotoData ( kXMP_PhotoshopFile, &this->xmpObj, this->exifMgr, this->iptcMgr, &this->psirMgr ); + + try { + XMP_OptionBits options = kXMP_UseCompactFormat; + if ( fileHadXMP ) options |= kXMP_ExactPacketLength; + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength ); + } catch ( ... ) { + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + // Decide whether to do an in-place update. This can only happen if all of the following are true: + // - There is an XMP packet in the file. + // - The are no changes to the legacy image resources. (The IPTC and EXIF are in the PSIR.) + // - The new XMP can fit in the old space. + + bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength)); if ( this->psirMgr.IsLegacyChanged() ) doInPlace = false; if ( doInPlace ) { @@ -340,7 +297,7 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) #if GatherPerformanceData sAPIPerf->back().extraInfo += ", PSD in-place update"; #endif - + if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { // They ought to match, cheap to be sure. size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); @@ -348,14 +305,14 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) } LFA_FileRef liveFile = this->parent->fileRef; - + 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() ); LFA_Seek ( liveFile, oldPacketOffset, SEEK_SET ); LFA_Write ( liveFile, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); - + } else { #if GatherPerformanceData @@ -364,16 +321,16 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) std::string origPath = this->parent->filePath; LFA_FileRef origRef = this->parent->fileRef; - + std::string updatePath; LFA_FileRef updateRef = 0; - + CreateTempFile ( origPath, &updatePath, kCopyMacRsrc ); updateRef = LFA_Open ( updatePath.c_str(), 'w' ); - + this->parent->filePath = updatePath; this->parent->fileRef = updateRef; - + try { XMP_Assert ( ! this->skipReconcile ); this->skipReconcile = true; @@ -382,21 +339,22 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) } catch ( ... ) { this->skipReconcile = false; LFA_Close ( updateRef ); + LFA_Delete ( updatePath.c_str() ); this->parent->filePath = origPath; this->parent->fileRef = origRef; throw; } - + LFA_Close ( origRef ); LFA_Delete ( origPath.c_str() ); - + LFA_Close ( updateRef ); LFA_Rename ( updatePath.c_str(), origPath.c_str() ); this->parent->filePath = origPath; this->parent->fileRef = 0; - + } - + this->needsUpdate = false; } // PSD_MetaHandler::UpdateFile @@ -412,55 +370,57 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) void PSD_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) { LFA_FileRef destRef = this->parent->fileRef; - + XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); - XMP_Uns32 sourceLen = (XMP_Uns32) LFA_Measure ( sourceRef ); + XMP_Uns64 sourceLen = LFA_Measure ( sourceRef ); if ( sourceLen == 0 ) return; // Tolerate empty files. - + // Reconcile the legacy metadata, unless this is called from UpdateFile. Reserialize the XMP to // get standard padding, PutXMP has probably done an in-place serialize. Set the XMP image resource. - + if ( ! skipReconcile ) { - ExportXMPtoJTP ( kXMP_PhotoshopFile, &this->xmpObj, this->exifMgr, &this->psirMgr, this->iptcMgr ); + // Update the IPTC-IIM and native TIFF/Exif metadata, and reserialize the now final XMP packet. + ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, &this->psirMgr ); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); } - + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); this->packetInfo.offset = kXMPFiles_UnknownOffset; this->packetInfo.length = (XMP_StringLen)this->xmpPacket.size(); FillPacketInfo ( this->xmpPacket, &this->packetInfo ); this->psirMgr.SetImgRsrc ( kPSIR_XMP, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); - + // Copy the file header and color mode section, then write the updated image resource section, // and copy the tail of the source file (layer and mask section to EOF). - + LFA_Seek ( sourceRef, 0, SEEK_SET ); LFA_Truncate (destRef, 0 ); - + LFA_Copy ( sourceRef, destRef, 26 ); // Copy the file header. - + XMP_Uns32 cmLen; LFA_Read ( sourceRef, &cmLen, 4 ); LFA_Write ( destRef, &cmLen, 4 ); // Copy the color mode section length. cmLen = GetUns32BE ( &cmLen ); LFA_Copy ( sourceRef, destRef, cmLen ); // Copy the color mode section contents. - + XMP_Uns32 irLen; LFA_Read ( sourceRef, &irLen, 4 ); // Get the source image resource section length. irLen = GetUns32BE ( &irLen ); - + this->psirMgr.UpdateFileResources ( sourceRef, destRef, 0, abortProc, abortArg ); - - XMP_Uns32 tailOffset = 26 + 4 + cmLen + 4 + irLen; - XMP_Uns32 tailLength = sourceLen - tailOffset; + + XMP_Uns64 tailOffset = 26 + 4 + cmLen + 4 + irLen; + XMP_Uns64 tailLength = sourceLen - tailOffset; LFA_Seek ( sourceRef, tailOffset, SEEK_SET ); LFA_Seek ( destRef, 0, SEEK_END ); LFA_Copy ( sourceRef, destRef, tailLength ); // Copy the tail of the file. - + this->needsUpdate = false; } // PSD_MetaHandler::WriteFile diff --git a/source/XMPFiles/FileHandlers/PSD_Handler.hpp b/source/XMPFiles/FileHandlers/PSD_Handler.hpp index 385d9bf..7ac56b5 100644 --- a/source/XMPFiles/FileHandlers/PSD_Handler.hpp +++ b/source/XMPFiles/FileHandlers/PSD_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -38,7 +38,6 @@ static const XMP_OptionBits kPSD_HandlerFlags = (kXMPFiles_CanInjectXMP | kXMPFiles_CanReconcile | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | - kXMPFiles_ReturnsTNail | kXMPFiles_AllowsSafeUpdate); class PSD_MetaHandler : public XMPFileHandler @@ -46,7 +45,6 @@ class PSD_MetaHandler : public XMPFileHandler public: void CacheFileData(); - void ProcessTNail(); void ProcessXMP(); void UpdateFile ( bool doSafeUpdate ); diff --git a/source/XMPFiles/FileHandlers/PostScript_Handler.cpp b/source/XMPFiles/FileHandlers/PostScript_Handler.cpp index 2b74646..e925833 100644 --- a/source/XMPFiles/FileHandlers/PostScript_Handler.cpp +++ b/source/XMPFiles/FileHandlers/PostScript_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -53,15 +53,15 @@ bool PostScript_CheckFormat ( XMP_FileFormat format, XMP_Int64 psOffset; size_t psLength; - long temp1, temp2; + XMP_Uns32 temp1, temp2; // Check for the binary EPSF preview header. LFA_Seek ( fileRef, 0, SEEK_SET ); if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; temp1 = GetUns32BE ( ioBuf.ptr ); - - if ( temp1 == (long)0xC5D0D3C6 ) { + + if ( temp1 == 0xC5D0D3C6 ) { if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return false; @@ -86,13 +86,12 @@ bool PostScript_CheckFormat ( XMP_FileFormat format, // Check the PostScript DSC major version number. - temp1 = LONG_MIN; // Will safely overflow if there are digits, remain negative if there aren't. + temp1 = 0; while ( (ioBuf.ptr < ioBuf.limit) && ('0' <= *ioBuf.ptr) && (*ioBuf.ptr <= '9') ) { temp1 = (temp1 * 10) + (*ioBuf.ptr - '0'); - if ( temp1 < 0 ) return false; // Overflow. + if ( temp1 > 1000 ) return false; // Overflow. ioBuf.ptr += 1; } - // if ( temp1 < 0 ) break; *** Covered by 3.0 check. if ( temp1 < 3 ) return false; // The version must be at least 3.0. if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return false; @@ -101,14 +100,13 @@ bool PostScript_CheckFormat ( XMP_FileFormat format, // Check the PostScript DSC minor version number. - temp2 = LONG_MIN; // Will safely overflow if there are digits, remain negative if there aren't. + temp2 = 0; while ( (ioBuf.ptr < ioBuf.limit) && ('0' <= *ioBuf.ptr) && (*ioBuf.ptr <= '9') ) { temp2 = (temp2 * 10) + (*ioBuf.ptr - '0'); - if ( temp2 < 0 ) return false; // Overflow. + if ( temp2 > 1000 ) return false; // Overflow. ioBuf.ptr += 1; } - if ( temp2 < 0 ) return false; // No digits or overflow. - // Note that we don't care about the actual minor version number. + // We don't care about the actual minor version number. if ( format == kXMP_PostScriptFile ) { @@ -128,13 +126,12 @@ bool PostScript_CheckFormat ( XMP_FileFormat format, // Check the EPS major version number. - temp1 = LONG_MIN; // Will safely overflow if there are digits, remain negative if there aren't. + temp1 = 0; while ( (ioBuf.ptr < ioBuf.limit) && ('0' <= *ioBuf.ptr) && (*ioBuf.ptr <= '9') ) { temp1 = (temp1 * 10) + (*ioBuf.ptr - '0'); - if ( temp1 < 0 ) return false; // Overflow. + if ( temp1 > 1000 ) return false; // Overflow. ioBuf.ptr += 1; } - // if ( temp1 < 0 ) break; *** Covered by 3.0 check. if ( temp1 < 3 ) return false; // The version must be at least 3.0. if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return false; @@ -143,14 +140,13 @@ bool PostScript_CheckFormat ( XMP_FileFormat format, // Check the EPS minor version number. - temp2 = LONG_MIN; // Will safely overflow if there are digits, remain negative if there aren't. + temp2 = 0; while ( (ioBuf.ptr < ioBuf.limit) && ('0' <= *ioBuf.ptr) && (*ioBuf.ptr <= '9') ) { temp2 = (temp2 * 10) + (*ioBuf.ptr - '0'); - if ( temp2 < 0 ) return false; // Overflow. + if ( temp2 > 1000 ) return false; // Overflow. ioBuf.ptr += 1; } - if ( temp2 < 0 ) return false; // No digits or overflow. - // Note that we don't care about the actual minor version number. + // 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; @@ -214,9 +210,9 @@ int PostScript_MetaHandler::FindPostScriptHint() LFA_Seek ( fileRef, 0, SEEK_SET ); if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; - long temp1 = GetUns32BE ( ioBuf.ptr ); + XMP_Uns32 temp1 = GetUns32BE ( ioBuf.ptr ); - if ( temp1 == (long)0xC5D0D3C6 ) { + if ( temp1 == 0xC5D0D3C6 ) { if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return false; @@ -442,7 +438,7 @@ bool PostScript_MetaHandler::FindLastPacket() // ------------------------------- // Pick the last the valid packet. - long snipCount = scanner.GetSnipCount(); + int snipCount = scanner.GetSnipCount(); XMPScanner::SnipInfoVector snips ( snipCount ); scanner.Report ( snips ); diff --git a/source/XMPFiles/FileHandlers/PostScript_Handler.hpp b/source/XMPFiles/FileHandlers/PostScript_Handler.hpp index 4170451..c0d4e37 100644 --- a/source/XMPFiles/FileHandlers/PostScript_Handler.hpp +++ b/source/XMPFiles/FileHandlers/PostScript_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/RIFF_Handler.cpp b/source/XMPFiles/FileHandlers/RIFF_Handler.cpp new file mode 100644 index 0000000..daa4455 --- /dev/null +++ b/source/XMPFiles/FileHandlers/RIFF_Handler.cpp @@ -0,0 +1,347 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "RIFF.hpp" +#include "RIFF_Handler.hpp" + +using namespace std; + +// ================================================================================================= +/// \file RIFF_Handler.cpp +/// \brief File format handler for RIFF. +// ================================================================================================= + +// ================================================================================================= +// RIFF_MetaHandlerCTor +// ==================== + +XMPFileHandler * RIFF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new RIFF_MetaHandler ( parent ); +} + +// ================================================================================================= +// RIFF_CheckFormat +// =============== +// +// An RIFF file must begin with "RIFF", a 4 byte length, then the chunkType (AVI or WAV) +// The specified length MUST (in practice: SHOULD) match fileSize-8, but we don't bother checking this here. + +bool RIFF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef file, + XMPFiles* parent ) +{ + IgnoreParam(format); IgnoreParam(parent); + XMP_Assert ( (format == kXMP_AVIFile) || (format == kXMP_WAVFile) ); + LFA_Rewind( file ); + + XMP_Uns8 chunkID[12]; + LFA_Read( file, chunkID, 12, true ); + if ( ! CheckBytes( &chunkID[0], "RIFF", 4 )) return false; + + if ( CheckBytes(&chunkID[8],"AVI ",4) && format == kXMP_AVIFile ) return true; + if ( CheckBytes(&chunkID[8],"WAVE",4) && format == kXMP_WAVFile ) return true; + + return false; +} // RIFF_CheckFormat + +// ================================================================================================= +// RIFF_MetaHandler::RIFF_MetaHandler +// ================================ + +RIFF_MetaHandler::RIFF_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; + this->handlerFlags = kRIFF_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + this->oldFileSize = this->newFileSize = this->trailingGarbageSize = 0; + this->level = 0; + this->listInfoChunk = this->listTdatChunk = 0; + this->dispChunk = this->bextChunk = this->cr8rChunk = this->prmlChunk = 0; + this->xmpChunk = 0; + this->lastChunk = 0; + this->hasListInfoINAM = false; +} + +// ================================================================================================= +// RIFF_MetaHandler::~RIFF_MetaHandler +// ================================= + +RIFF_MetaHandler::~RIFF_MetaHandler() +{ + while ( ! this->riffChunks.empty() ) + { + delete this->riffChunks.back(); + this->riffChunks.pop_back(); + } +} + +// ================================================================================================= +// RIFF_MetaHandler::CacheFileData +// ============================== + +void RIFF_MetaHandler::CacheFileData() +{ + this->containsXMP = false; //assume for now + + LFA_FileRef file = this->parent->fileRef; + this->oldFileSize = LFA_Measure( file ); + if ( (this->parent->format == kXMP_WAVFile) && (this->oldFileSize > 0xFFFFFFFF) ) + XMP_Throw ( "RIFF_MetaHandler::CacheFileData: WAV Files larger 4GB not supported", kXMPErr_Unimplemented ); + + LFA_Rewind( file ); + this->level = 0; + + // parse top-level chunks (most likely just one, except large avi files) + XMP_Int64 filePos = 0; + while ( filePos < this->oldFileSize ) + { + + this->riffChunks.push_back( (RIFF::ContainerChunk*) RIFF::getChunk( NULL, this ) ); + + // Tolerate limited forms of trailing garbage in a file. Some apps append private data. + + filePos = LFA_Tell( file ); + XMP_Int64 fileTail = this->oldFileSize - filePos; + + if ( fileTail != 0 ) { + + if ( fileTail < 12 ) { + + this->oldFileSize = filePos; // Pretend the file is smaller. + this->trailingGarbageSize = fileTail; + + } else if ( this->parent->format == kXMP_WAVFile ) { + + if ( fileTail < 1024*1024 ) { + this->oldFileSize = filePos; // Pretend the file is smaller. + this->trailingGarbageSize = fileTail; + } else { + XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat ) + } + + } else { + + XMP_Int32 chunkInfo [3]; + LFA_Read ( file, &chunkInfo, 12, kLFA_RequireAll ); + LFA_Seek ( file, -12, SEEK_CUR ); + if ( (GetUns32LE ( &chunkInfo[0] ) != RIFF::kChunk_RIFF) || (GetUns32LE ( &chunkInfo[2] ) != RIFF::kType_AVIX) ) { + if ( fileTail < 1024*1024 ) { + this->oldFileSize = filePos; // Pretend the file is smaller. + this->trailingGarbageSize = fileTail; + } else { + XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat ) + } + } + + } + + } + + } + + // covered before => internal error if it occurs + XMP_Validate( LFA_Tell( file ) == this->oldFileSize, + "RIFF_MetaHandler::CacheFileData: unknown data at end of file", + kXMPErr_InternalFailure ); + +} // RIFF_MetaHandler::CacheFileData + +// ================================================================================================= +// RIFF_MetaHandler::ProcessXMP +// ============================ + +void RIFF_MetaHandler::ProcessXMP() +{ + SXMPUtils::RemoveProperties ( &this->xmpObj, 0, 0, kXMPUtil_DoAllProperties ); + // process physical packet first + if ( this->containsXMP ) this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + // then import native properties: + RIFF::importProperties( this ); + this->processedXMP = true; +} + +// ================================================================================================= +// RIFF_MetaHandler::UpdateFile +// =========================== + +void RIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + XMP_Validate( this->needsUpdate, "nothing to update", kXMPErr_InternalFailure ); + + //////////////////////////////////////////////////////////////////////////////////////// + //////////// PASS 1: basics, exports, packet reserialze + LFA_FileRef file = this->parent->fileRef; + RIFF::containerVect *rc = &this->riffChunks; + + //temptemp + //printf( "BEFORE:\n%s\n", rc->at(0)->toString().c_str() ); + + XMP_Enforce( rc->size() >= 1); + RIFF::ContainerChunk* mainChunk = rc->at(0); + this->lastChunk = rc->at( rc->size() - 1 ); // (may or may not coincide with mainChunk: ) + XMP_Enforce( mainChunk != NULL ); + + RIFF::relocateWronglyPlacedXMPChunk( this ); + // [2435625] lifted disablement for AVI + RIFF::exportAndRemoveProperties( this ); + + // always rewrite both LISTs (implicit size changes, e.g. through 0-term corrections may + // have very well led to size changes...) + // set XMP packet info, re-serialize + this->packetInfo.charForm = stdCharForm; + this->packetInfo.writeable = true; + this->packetInfo.offset = kXMPFiles_UnknownOffset; + this->packetInfo.length = kXMPFiles_UnknownLength; + + // re-serialization ( needed because of above exportAndRemoveProperties() ) + try { + if ( this->xmpChunk == 0 ) // new chunk? pad with 2K + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_NoOptions , 2048 ); + else // otherwise try to match former size + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_ExactPacketLength , (XMP_Uns32) this->xmpChunk->oldSize-8 ); + } catch ( ... ) { // if that fails, be happy with whatever. + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_NoOptions ); + } + + if ( (this->xmpPacket.size() & 1) == 1 ) this->xmpPacket += ' '; // Force the XMP to have an even size. + + // if missing, add xmp packet at end: + if( this->xmpChunk == 0 ) + this->xmpChunk = new RIFF::XMPChunk( this->lastChunk ); + // * parenting happens within this call. + // * size computation will happen in XMPChunk::changesAndSize() + // * actual data will be set in XMPChunk::write() + + //////////////////////////////////////////////////////////////////////////////////////// + // PASS 2: compute sizes, optimize container structure (no writing yet) + { + this->newFileSize = 0; + + // note: going through forward (not vice versa) is an important condition, + // so that parking LIST:Tdat,Cr8r, PrmL to last chunk is doable + // when encountered en route + for ( XMP_Uns32 chunkNo = 0; chunkNo < rc->size(); chunkNo++ ) + { + RIFF::Chunk* cur = rc->at(chunkNo); + cur->changesAndSize( this ); + this->newFileSize += cur->newSize; + if ( this->newFileSize % 2 == 1 ) this->newFileSize++; // pad byte + } + this->newFileSize += this->trailingGarbageSize; + } // PASS2 + + //////////////////////////////////////////////////////////////////////////////////////// + // PASS 2a: verify no chunk violates 2GB boundaries + switch( this->parent->format ) + { + // NB: <4GB for ALL chunks asserted before in ContainerChunk::changesAndSize() + + case kXMP_AVIFile: + // ensure no chunk (single or multi, no matter) crosses 2 GB ... + for ( XMP_Int32 chunkNo = 0; chunkNo < (XMP_Int32)rc->size(); chunkNo++ ) + { + if ( rc->at(chunkNo)->oldSize <= 0x80000000LL ) // ... if <2GB before + XMP_Validate( rc->at(chunkNo)->newSize <= 0x80000000LL, + "Chunk grew beyond 2 GB", kXMPErr_Unimplemented ); + } + + // compatibility: if single-chunk AND below <1GB, ensure <1GB + if ( ( rc->size() > 1 ) && ( rc->at(0)->oldSize < 0x40000000 ) ) + { + XMP_Validate( rc->at(0)->newSize < 0x40000000LL, "compatibility: mainChunk must remain < 1GB" , kXMPErr_Unimplemented ); + } + + // [2473381] compatibility: if single-chunk AND >2GB,<4GB, ensure <4GB + if ( ( rc->size() > 1 ) && + ( rc->at(0)->oldSize > 0x80000000LL ) && // 2GB + ( rc->at(0)->oldSize < 0x100000000LL ) ) // 4GB + { + XMP_Validate( rc->at(0)->newSize < 0x100000000LL, "compatibility: mainChunk must remain < 4GB" , kXMPErr_Unimplemented ); + } + + break; + + case kXMP_WAVFile: + XMP_Validate( 1 == rc->size(), "WAV must be single-chunk", kXMPErr_InternalFailure ); + XMP_Validate( rc->at(0)->newSize <= 0xFFFFFFFFLL, "WAV above 4 GB not supported", kXMPErr_Unimplemented ); + break; + + default: + XMP_Throw( "unknown format", kXMPErr_InternalFailure ); + } + + //////////////////////////////////////////////////////////////////////////////////////// + // PASS 3: write avix chunk(s) if applicable (shrinks or stays) + // and main chunk. -- operation order depends on mainHasShrunk. + { + // if needed, extend file beforehand + if ( this->newFileSize > this->oldFileSize ) LFA_Extend( file, newFileSize ); + + RIFF::Chunk* mainChunk = rc->at(0); + + XMP_Int64 mainGrowth = mainChunk->newSize - mainChunk->oldSize; + XMP_Enforce( mainGrowth >= 0 ); // main always stays or grows + + //temptemp + //printf( "AFTER:\n%s\n", rc->at(0)->toString().c_str() ); + + if ( rc->size() > 1 ) // [2457482] + XMP_Validate( mainGrowth == 0, "mainChunk must not grow, if multiple RIFF chunks", kXMPErr_InternalFailure ); + + // back to front: + + XMP_Int64 avixStart = newFileSize; // count from the back + + if ( this->trailingGarbageSize != 0 ) { + XMP_Int64 goodDataEnd = this->newFileSize - this->trailingGarbageSize; + LFA_Move ( file, this->oldFileSize, file, goodDataEnd, this->trailingGarbageSize ); + avixStart = goodDataEnd; + } + + for ( XMP_Int32 chunkNo = ((XMP_Int32)rc->size()) -1; chunkNo >= 0; chunkNo-- ) + { + RIFF::Chunk* cur = rc->at(chunkNo); + + avixStart -= cur->newSize; + if ( avixStart % 2 == 1 ) // rewind one more + avixStart -= 1; + + LFA_Seek( file, avixStart , SEEK_SET ); + + if ( cur->hasChange ) // need explicit write-out ? + cur->write( this, file, chunkNo == 0 ); + else // or will a simple move do? + { + XMP_Enforce( cur->oldSize == cur->newSize ); + if ( cur->oldPos != avixStart ) // important optimization: only move if there's a need to + LFA_Move( file, cur->oldPos, file, avixStart, cur->newSize ); + } + } + + // if needed, shrink file afterwards + if ( this->newFileSize < this->oldFileSize ) LFA_Truncate( file, this->newFileSize ); + } // PASS 3 + + this->needsUpdate = false; //do last for safety +} // RIFF_MetaHandler::UpdateFile + +// ================================================================================================= +// RIFF_MetaHandler::WriteFile +// ========================== + +void RIFF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, + const std::string & sourcePath ) +{ + IgnoreParam(sourceRef); IgnoreParam(sourcePath); + XMP_Throw ( "RIFF_MetaHandler::WriteFile: Not supported (must go through UpdateFile", kXMPErr_Unavailable ); +} + diff --git a/source/XMPFiles/FileHandlers/RIFF_Handler.hpp b/source/XMPFiles/FileHandlers/RIFF_Handler.hpp new file mode 100644 index 0000000..093fd63 --- /dev/null +++ b/source/XMPFiles/FileHandlers/RIFF_Handler.hpp @@ -0,0 +1,70 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= +#ifndef __RIFF_Handler_hpp__ +#define __RIFF_Handler_hpp__ 1 + +#include "XMP_Const.h" +#include "XMPFiles_Impl.hpp" +#include "RIFF_Support.hpp" + +// ================================================================================================= +/// \file RIFF_Handler.hpp +/// \brief File format handler for RIFF (AVI, WAV). +// ================================================================================================= + +extern XMPFileHandler * RIFF_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool RIFF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kRIFF_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_CanReconcile + ); + +class RIFF_MetaHandler : public XMPFileHandler +{ +public: + RIFF_MetaHandler ( XMPFiles* parent ); + ~RIFF_MetaHandler(); + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); + + //////////////////////////////////////////////////////////////////////////////////// + // instance vars + // most often just one RIFF:* (except for AVI,[AVIX] >1 GB) + std::vector<RIFF::ContainerChunk*> riffChunks; + XMP_Int64 oldFileSize, newFileSize, trailingGarbageSize; + + // state variables, needed during parsing + XMP_Uns8 level; + + RIFF::ContainerChunk *listInfoChunk, *listTdatChunk; + RIFF::ValueChunk* dispChunk; + RIFF::ValueChunk* bextChunk; + RIFF::ValueChunk* cr8rChunk; + RIFF::ValueChunk* prmlChunk; + RIFF::XMPChunk* xmpChunk; + RIFF::ContainerChunk* lastChunk; + bool hasListInfoINAM; // needs to be known for the special 3-way merge around dc:title + +}; // RIFF_MetaHandler + +// ================================================================================================= + +#endif /* __RIFF_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/SWF_Handler.cpp b/source/XMPFiles/FileHandlers/SWF_Handler.cpp index 100f79e..9ae2b25 100644 --- a/source/XMPFiles/FileHandlers/SWF_Handler.cpp +++ b/source/XMPFiles/FileHandlers/SWF_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -130,17 +130,6 @@ void SWF_MetaHandler::CacheFileData() } // SWF_MetaHandler::CacheFileData // ================================================================================================= -// SWF_MetaHandler::ProcessTNail -// ============================== - -void SWF_MetaHandler::ProcessTNail() -{ - - XMP_Throw ( "SWF_MetaHandler::ProcessTNail isn't implemented yet", kXMPErr_Unimplemented ); - -} // SWF_MetaHandler::ProcessTNail - -// ================================================================================================= // SWF_MetaHandler::ProcessXMP // ============================ // diff --git a/source/XMPFiles/FileHandlers/SWF_Handler.hpp b/source/XMPFiles/FileHandlers/SWF_Handler.hpp index 511ddd6..91ad760 100644 --- a/source/XMPFiles/FileHandlers/SWF_Handler.hpp +++ b/source/XMPFiles/FileHandlers/SWF_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -29,23 +29,21 @@ extern XMPFileHandler* SWF_MetaHandlerCTor ( XMPFiles* parent ); extern bool SWF_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef fileRef, - XMPFiles* parent ); + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ); static const XMP_OptionBits kSWF_HandlerFlags = ( kXMPFiles_CanInjectXMP | kXMPFiles_CanExpand | kXMPFiles_PrefersInPlace | kXMPFiles_AllowsOnlyXMP | - kXMPFiles_ReturnsRawPacket - ); + kXMPFiles_ReturnsRawPacket ); class SWF_MetaHandler : public XMPFileHandler { public: void CacheFileData(); - void ProcessTNail(); void ProcessXMP(); XMP_OptionBits GetSerializeOptions(); @@ -56,8 +54,6 @@ public: SWF_MetaHandler ( XMPFiles* parent ); virtual ~SWF_MetaHandler(); - - }; // SWF_MetaHandler // ================================================================================================= diff --git a/source/XMPFiles/FileHandlers/Scanner_Handler.cpp b/source/XMPFiles/FileHandlers/Scanner_Handler.cpp index 4959ca0..1af7326 100644 --- a/source/XMPFiles/FileHandlers/Scanner_Handler.cpp +++ b/source/XMPFiles/FileHandlers/Scanner_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -107,9 +107,6 @@ PickMainPacket ( std::vector<CandidateInfo>& candidates, bool beLenient ) for ( child = 0; child < (int)candidates.size(); ++child ) { if ( pruned[child] || (child == pkt) ) continue; // Skip already pruned ones and self. -#if 0 // *** Disable for now, SXMPUtils::HasContainedDoc is Adobe private. - pruned[child] = SXMPUtils::HasContainedDoc ( *candidates[pkt].xmpObj, *candidates[child].xmpObj ); -#endif } } diff --git a/source/XMPFiles/FileHandlers/Scanner_Handler.hpp b/source/XMPFiles/FileHandlers/Scanner_Handler.hpp index 625b565..a6f23b5 100644 --- a/source/XMPFiles/FileHandlers/Scanner_Handler.hpp +++ b/source/XMPFiles/FileHandlers/Scanner_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp b/source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp index 2dca918..d3758da 100644 --- a/source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp +++ b/source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -134,16 +134,16 @@ bool SonyHDV_CheckFormat ( XMP_FileFormat format, clipName = leafName; // Set tempPath to <root>/<clip-name>, e.g. ".../MyMovie/00_0001_2007-08-06_165555". This is - // passed to the handler via the handlerTemp hackery. + // passed to the handler via the tempPtr hackery. tempPath = rootPath; tempPath += kDirChar; tempPath += clipName; size_t pathLen = tempPath.size() + 1; // Include a terminating nul. - parent->handlerTemp = malloc ( pathLen ); - if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for SonyHDV clip info", kXMPErr_NoMemory ); - memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. + parent->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for SonyHDV clip info", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. return true; @@ -486,11 +486,11 @@ SonyHDV_MetaHandler::SonyHDV_MetaHandler ( XMPFiles * _parent ) // Extract the root path and clip name. - XMP_Assert ( this->parent->handlerTemp != 0 ); + XMP_Assert ( this->parent->tempPtr != 0 ); - this->rootPath.assign ( (char*) this->parent->handlerTemp ); - free ( this->parent->handlerTemp ); - this->parent->handlerTemp = 0; + this->rootPath.assign ( (char*) this->parent->tempPtr ); + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; SplitLeafName ( &this->rootPath, &this->clipName ); @@ -503,9 +503,9 @@ SonyHDV_MetaHandler::SonyHDV_MetaHandler ( XMPFiles * _parent ) SonyHDV_MetaHandler::~SonyHDV_MetaHandler() { - if ( this->parent->handlerTemp != 0 ) { - free ( this->parent->handlerTemp ); - this->parent->handlerTemp = 0; + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; } } // SonyHDV_MetaHandler::~SonyHDV_MetaHandler @@ -630,7 +630,7 @@ void SonyHDV_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) void SonyHDV_MetaHandler::CacheFileData() { - XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + XMP_Assert ( ! this->containsXMP ); // See if the clip's .XMP file exists. diff --git a/source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp b/source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp index a69f0dc..2af4ffc 100644 --- a/source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp +++ b/source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/TIFF_Handler.cpp b/source/XMPFiles/FileHandlers/TIFF_Handler.cpp index a12e718..15332e6 100644 --- a/source/XMPFiles/FileHandlers/TIFF_Handler.cpp +++ b/source/XMPFiles/FileHandlers/TIFF_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -13,6 +13,7 @@ #include "PSIR_Support.hpp" #include "IPTC_Support.hpp" #include "ReconcileLegacy.hpp" +#include "Reconcile_Impl.hpp" #include "MD5.h" @@ -42,19 +43,19 @@ bool TIFF_CheckFormat ( XMP_FileFormat format, { IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent); XMP_Assert ( format == kXMP_TIFFFile ); - + enum { kMinimalTIFFSize = 4+4+2+12+4 }; // Header plus IFD with 1 entry. IOBuffer ioBuf; - + LFA_Seek ( fileRef, 0, SEEK_SET ); if ( ! CheckFileSpace ( fileRef, &ioBuf, kMinimalTIFFSize ) ) return false; - + bool leTIFF = CheckBytes ( ioBuf.ptr, "\x49\x49\x2A\x00", 4 ); bool beTIFF = CheckBytes ( ioBuf.ptr, "\x4D\x4D\x00\x2A", 4 ); - + return (leTIFF | beTIFF); - + } // TIFF_CheckFormat // ================================================================================================= @@ -106,24 +107,24 @@ void TIFF_MetaHandler::CacheFileData() { LFA_FileRef fileRef = this->parent->fileRef; XMP_PacketInfo & packetInfo = this->packetInfo; - + XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; const bool checkAbort = (abortProc != 0); - - XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + + XMP_Assert ( ! this->containsXMP ); // Set containsXMP to true here only if the XMP tag is found. - + if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "TIFF_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); } - + this->tiffMgr.ParseFileStream ( fileRef ); - + TIFF_Manager::TagInfo dngInfo; if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, &dngInfo ) ) { - // Reject DNG files that are version 2.0 or beyond, this is being written at the time of + // Reject DNG files that are version 2.0 or beyond, this is being written at the time of // DNG version 1.2. The DNG team says it is OK to use 2.0, not strictly 1.2. Use the // DNGBackwardVersion if it is present, else the DNGVersion. Note that the version value is // supposed to be type BYTE, so the file order is always essentially big endian. @@ -135,10 +136,10 @@ void TIFF_MetaHandler::CacheFileData() if ( majorVersion > 1 ) XMP_Throw ( "DNG version beyond 1.x", kXMPErr_BadTIFF ); } - + TIFF_Manager::TagInfo xmpInfo; bool found = this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, &xmpInfo ); - + if ( found ) { this->packetInfo.offset = this->tiffMgr.GetValueOffset ( kTIFF_PrimaryIFD, kTIFF_XMP ); @@ -152,27 +153,8 @@ void TIFF_MetaHandler::CacheFileData() this->containsXMP = true; } - -} // TIFF_MetaHandler::CacheFileData - -// ================================================================================================= -// TIFF_MetaHandler::ProcessTNail -// ============================== -// -// Do the same processing as JPEG, even though Exif says that uncompressed images can only have -// uncompressed thumbnails. Who know what someone might write? - -// *** Should extract this code into a utility shared with the JPEG handler. -void TIFF_MetaHandler::ProcessTNail() -{ - this->processedTNail = true; // Make sure we only come through here once. - this->containsTNail = false; // Set it to true after all of the info is gathered. - - this->containsTNail = this->tiffMgr.GetTNailInfo ( &this->tnailInfo ); - if ( this->containsTNail ) this->tnailInfo.fileFormat = this->parent->format; - -} // TIFF_MetaHandler::ProcessTNail +} // TIFF_MetaHandler::CacheFileData // ================================================================================================= // TIFF_MetaHandler::ProcessXMP @@ -184,29 +166,19 @@ void TIFF_MetaHandler::ProcessTNail() void TIFF_MetaHandler::ProcessXMP() { - + this->processedXMP = true; // Make sure we only come through here once. // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy // import if the XMP packet gets parsing errors. - // Parse the IPTC and PSIR, determine the last-legacy priority. For TIFF files the relevant - // legacy priorities (ignoring Mac pnot and ANPA resources) are: - // kLegacyJTP_TIFF_IPTC - highest - // kLegacyJTP_TIFF_TIFF_Tags - // kLegacyJTP_PSIR_OldCaption - // kLegacyJTP_PSIR_IPTC - yes, a TIFF file can have the IPTC in 2 places - // kLegacyJTP_None - lowest - // ! Photoshop 6 wrote annoyingly wacky TIFF files. It buried a lot of the Exif metadata inside // ! image resource 1058, itself inside of tag 34377 in the 0th IFD. Take care of this before // ! doing any of the legacy metadata presence or priority analysis. Delete image resource 1058 // ! to get rid of the buried Exif, but don't mark the XMPFiles object as changed. This change // ! should not trigger an update, but should be included as part of a normal update. - - bool found; - RecJTP_LegacyPriority lastLegacy = kLegacyJTP_None; + bool found; bool readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0); if ( readOnly ) { @@ -214,24 +186,16 @@ void TIFF_MetaHandler::ProcessXMP() this->iptcMgr = new IPTC_Reader(); } else { this->psirMgr = new PSIR_FileWriter(); - #if ! XMP_UNIXBuild - this->iptcMgr = new IPTC_Writer(); // ! Parse it later. - #else - // ! Hack until the legacy-as-local issues are resolved for generic UNIX. - this->iptcMgr = new IPTC_Reader(); // ! Import IPTC but don't export it. - #endif + this->iptcMgr = new IPTC_Writer(); // ! Parse it later. } TIFF_Manager & tiff = this->tiffMgr; // Give the compiler help in recognizing non-aliases. PSIR_Manager & psir = *this->psirMgr; IPTC_Manager & iptc = *this->iptcMgr; - + TIFF_Manager::TagInfo psirInfo; bool havePSIR = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_PSIR, &psirInfo ); - - TIFF_Manager::TagInfo iptcInfo; - bool haveIPTC = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_IPTC, &iptcInfo ); // The TIFF IPTC tag. - + if ( havePSIR ) { // ! Do the Photoshop 6 integration before other legacy analysis. psir.ParseMemoryResources ( psirInfo.dataPtr, psirInfo.dataLen ); PSIR_Manager::ImgRsrcInfo buriedExif; @@ -241,46 +205,51 @@ void TIFF_MetaHandler::ProcessXMP() if ( ! readOnly ) psir.DeleteImgRsrc ( kPSIR_Exif ); } } - - if ( haveIPTC ) { // At this point "haveIPTC" means from TIFF tag 33723. - iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); - lastLegacy = kLegacyJTP_TIFF_IPTC; - } - - if ( lastLegacy < kLegacyJTP_TIFF_TIFF_Tags ) { - found = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_ImageDescription, 0 ); - if ( ! found ) found = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_Artist, 0 ); - if ( ! found ) found = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_Copyright, 0 ); - if ( found ) lastLegacy = kLegacyJTP_TIFF_TIFF_Tags; - } - - if ( havePSIR ) { - if ( lastLegacy < kLegacyJTP_PSIR_OldCaption ) { - found = psir.GetImgRsrc ( kPSIR_OldCaption, 0 ); - if ( ! found ) found = psir.GetImgRsrc ( kPSIR_OldCaptionPStr, 0 ); - if ( found ) lastLegacy = kLegacyJTP_PSIR_OldCaption; - } + TIFF_Manager::TagInfo iptcInfo; + bool haveIPTC = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_IPTC, &iptcInfo ); // The TIFF IPTC tag. + int iptcDigestState = kDigestMatches; - if ( ! haveIPTC ) { - PSIR_Manager::ImgRsrcInfo iptcInfo; - haveIPTC = psir.GetImgRsrc ( kPSIR_IPTC, &iptcInfo ); - if ( haveIPTC ) { - iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); - if ( lastLegacy < kLegacyJTP_PSIR_IPTC ) lastLegacy = kLegacyJTP_PSIR_IPTC; + if ( haveIPTC ) { + + bool haveDigest = false; + PSIR_Manager::ImgRsrcInfo digestInfo; + if ( havePSIR ) haveDigest = psir.GetImgRsrc ( kPSIR_IPTCDigest, &digestInfo ); + if ( digestInfo.dataLen != 16 ) haveDigest = false; + + if ( ! haveDigest ) { + + iptcDigestState = kDigestMissing; + + } else { + + // Older versions of Photoshop wrote tag 33723 with type LONG, but ignored the trailing + // zero padding for the IPTC digest. If the full digest differs, recheck without the padding. + + iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, iptcInfo.dataLen, digestInfo.dataPtr ); + + if ( (iptcDigestState == kDigestDiffers) && (kTIFF_TypeSizes[iptcInfo.type] > 1) ) { + XMP_Uns8 * endPtr = (XMP_Uns8*)iptcInfo.dataPtr + iptcInfo.dataLen - 1; + XMP_Uns8 * minPtr = endPtr - kTIFF_TypeSizes[iptcInfo.type] + 1; + while ( (endPtr >= minPtr) && (*endPtr == 0) ) --endPtr; + XMP_Uns32 unpaddedLen = (XMP_Uns32) (endPtr - (XMP_Uns8*)iptcInfo.dataPtr + 1); + iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, unpaddedLen, digestInfo.dataPtr ); } + } } - + XMP_OptionBits options = k2XMP_FileHadExif; // TIFF files are presumed to have Exif legacy. + if ( haveIPTC ) options |= k2XMP_FileHadIPTC; if ( this->containsXMP ) options |= k2XMP_FileHadXMP; - if ( haveIPTC || (lastLegacy == kLegacyJTP_PSIR_OldCaption) ) options |= k2XMP_FileHadIPTC; // Process the XMP packet. If it fails to parse, do a forced legacy import but still throw an // exception. This tells the caller that an error happened, but gives them recovered legacy // should they want to proceed with that. + bool haveXMP = false; + if ( ! this->xmpPacket.empty() ) { XMP_Assert ( this->containsXMP ); // Common code takes care of packetInfo.charForm, .padSize, and .writeable. @@ -288,16 +257,23 @@ 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 ); - ImportJTPtoXMP ( kXMP_TIFFFile, lastLegacy, &tiff, psir, &iptc, &this->xmpObj, options ); + if ( haveIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); + if ( iptcDigestState == kDigestMatches ) iptcDigestState = kDigestMissing; + ImportPhotoData ( tiff, iptc, psir, iptcDigestState, &this->xmpObj, options ); throw; // ! Rethrow the exception, don't absorb it. } } // Process the legacy metadata. - ImportJTPtoXMP ( kXMP_TIFFFile, lastLegacy, &tiff, psir, &iptc, &this->xmpObj, options ); + if ( haveIPTC && (! haveXMP) && (iptcDigestState == kDigestMatches) ) iptcDigestState = kDigestMissing; + bool parseIPTC = (iptcDigestState != kDigestMatches) || (! readOnly); + if ( parseIPTC ) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); + ImportPhotoData ( tiff, iptc, psir, iptcDigestState, &this->xmpObj, options ); + this->containsXMP = true; // Assume we now have something in the XMP. } // TIFF_MetaHandler::ProcessXMP @@ -320,57 +296,63 @@ void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; - // Decide whether to do an in-place update. This can only happen if all of the following are true: - // - There is an XMP packet in the file. - // - The are no changes to the legacy tags. (The IPTC and PSIR are in the TIFF tags.) - // - The new XMP can fit in the old space. - - ExportXMPtoJTP ( kXMP_TIFFFile, &this->xmpObj, &this->tiffMgr, this->psirMgr, this->iptcMgr ); - XMP_Int64 oldPacketOffset = this->packetInfo.offset; XMP_Int32 oldPacketLength = this->packetInfo.length; - + if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; + + bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0)); + + // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and + // exif: copies from the XMP, so reserialize the now final XMP packet. + + ExportPhotoData ( kXMP_TIFFFile, &this->xmpObj, &this->tiffMgr, this->iptcMgr, this->psirMgr ); - bool doInPlace = (this->xmpPacket.size() <= (size_t)this->packetInfo.length); + try { + XMP_OptionBits options = kXMP_UseCompactFormat; + if ( fileHadXMP ) options |= kXMP_ExactPacketLength; + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength ); + } catch ( ... ) { + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + // Decide whether to do an in-place update. This can only happen if all of the following are true: + // - There is an XMP packet in the file. + // - The are no changes to the legacy tags. (The IPTC and PSIR are in the TIFF tags.) + // - The new XMP can fit in the old space. + + bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength)); if ( this->tiffMgr.IsLegacyChanged() ) doInPlace = false; - if ( doInPlace ) { + if ( ! doInPlace ) { + + #if GatherPerformanceData + sAPIPerf->back().extraInfo += ", TIFF append update"; + #endif + + this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, (XMP_Uns32)this->xmpPacket.size(), this->xmpPacket.c_str() ); + this->tiffMgr.UpdateFileStream ( destRef ); + + } else { #if GatherPerformanceData sAPIPerf->back().extraInfo += ", TIFF in-place update"; #endif - + if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { // They ought to match, cheap to be sure. size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); this->xmpPacket.append ( extraSpace, ' ' ); } - + LFA_FileRef liveFile = this->parent->fileRef; - + XMP_Assert ( this->xmpPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic. - + LFA_Seek ( liveFile, oldPacketOffset, SEEK_SET ); LFA_Write ( liveFile, this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() ); - - } else { - #if GatherPerformanceData - sAPIPerf->back().extraInfo += ", TIFF append update"; - #endif - - // Reserialize the XMP to get standard padding, PutXMP has probably done an in-place serialize. - this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); - this->packetInfo.offset = kXMPFiles_UnknownOffset; - this->packetInfo.length = (XMP_Int32)this->xmpPacket.size(); - FillPacketInfo ( this->xmpPacket, &this->packetInfo ); - - this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, (XMP_Uns32)this->xmpPacket.size(), this->xmpPacket.c_str() ); - - this->tiffMgr.UpdateFileStream ( destRef ); - } this->needsUpdate = false; @@ -389,12 +371,12 @@ void TIFF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so LFA_FileRef destRef = this->parent->fileRef; XMP_AbortProc abortProc = this->parent->abortProc; void * abortArg = this->parent->abortArg; - + XMP_Int64 fileLen = LFA_Measure ( sourceRef ); if ( fileLen > 0xFFFFFFFFLL ) { // Check before making a copy of the file. XMP_Throw ( "TIFF fles can't exceed 4GB", kXMPErr_BadTIFF ); } - + LFA_Seek ( sourceRef, 0, SEEK_SET ); LFA_Truncate ( destRef, 0 ); LFA_Copy ( sourceRef, destRef, fileLen, abortProc, abortArg ); diff --git a/source/XMPFiles/FileHandlers/TIFF_Handler.hpp b/source/XMPFiles/FileHandlers/TIFF_Handler.hpp index 01c1e06..aabb509 100644 --- a/source/XMPFiles/FileHandlers/TIFF_Handler.hpp +++ b/source/XMPFiles/FileHandlers/TIFF_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -38,7 +38,6 @@ static const XMP_OptionBits kTIFF_HandlerFlags = (kXMPFiles_CanInjectXMP | kXMPFiles_CanReconcile | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | - kXMPFiles_ReturnsTNail | kXMPFiles_AllowsSafeUpdate); class TIFF_MetaHandler : public XMPFileHandler @@ -46,7 +45,6 @@ class TIFF_MetaHandler : public XMPFileHandler public: void CacheFileData(); - void ProcessTNail(); void ProcessXMP(); void UpdateFile ( bool doSafeUpdate ); diff --git a/source/XMPFiles/FileHandlers/Trivial_Handler.cpp b/source/XMPFiles/FileHandlers/Trivial_Handler.cpp index c96c944..3ebdb67 100644 --- a/source/XMPFiles/FileHandlers/Trivial_Handler.cpp +++ b/source/XMPFiles/FileHandlers/Trivial_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/Trivial_Handler.hpp b/source/XMPFiles/FileHandlers/Trivial_Handler.hpp index 9729ba0..9db851c 100644 --- a/source/XMPFiles/FileHandlers/Trivial_Handler.hpp +++ b/source/XMPFiles/FileHandlers/Trivial_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/UCF_Handler.cpp b/source/XMPFiles/FileHandlers/UCF_Handler.cpp index e9c2219..cea781e 100644 --- a/source/XMPFiles/FileHandlers/UCF_Handler.cpp +++ b/source/XMPFiles/FileHandlers/UCF_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -116,7 +116,11 @@ bool UCF_CheckFormat ( XMP_FileFormat format, XMP_LitMatch( mimetype, "application/vnd.adobe.x-mars" ) || //Mars plugin(labs only), Acrobat8 XMP_LitMatch( mimetype, "application/vnd.adobe.pdfxml" ) || //Mars plugin(labs only), Acrobat 9 XMP_LitMatch( mimetype, "vnd.adobe.x-asnd" ) || //Adobe Sound Document (Soundbooth Team) - XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-idml-package" ) ) //inCopy (inDesign) IDML Document + XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-idml-package" ) || //inCopy (inDesign) IDML Document + XMP_LitMatch( mimetype, "application/vnd.adobe.incopy-package" ) || // InDesign Document + XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-package" ) || // InDesign Document + + false ) // "sentinel" // *** ==> unknown are also treated as not acceptable okMimetype = true; @@ -129,7 +133,7 @@ bool UCF_CheckFormat ( XMP_FileFormat format, //.airi - temporary Adobe Air Files //application/vnd.adobe.air-application-intermediate-package+zip - delete mimetype; + delete [] mimetype; return okMimetype; } // UCF_CheckFormat diff --git a/source/XMPFiles/FileHandlers/UCF_Handler.hpp b/source/XMPFiles/FileHandlers/UCF_Handler.hpp index ec60f86..2f9af5e 100644 --- a/source/XMPFiles/FileHandlers/UCF_Handler.hpp +++ b/source/XMPFiles/FileHandlers/UCF_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FileHandlers/WAV_Handler.cpp b/source/XMPFiles/FileHandlers/WAV_Handler.cpp deleted file mode 100644 index 6820d01..0000000 --- a/source/XMPFiles/FileHandlers/WAV_Handler.cpp +++ /dev/null @@ -1,707 +0,0 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -#include "XMP_Environment.h" // ! This must be the first include. -#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed. - -#if XMP_WinBuild - #pragma warning ( disable : 4996 ) // '...' was declared deprecated -#endif - -#include "WAV_Handler.hpp" -#include "RIFF_Support.hpp" -#include "Reconcile_Impl.hpp" -#include "XMP_Const.h" - -using namespace std; - -#define kXMPUserDataType MakeFourCC ( '_', 'P', 'M', 'X' ) /* Yes, backwards! */ - -#define formtypeWAVE MakeFourCC('W', 'A', 'V', 'E') - - -// ------------------------------------------------------------------------------------------------- -// Premiere Pro specific info for reconciliation - -// ! MakeFourCC warning: The MakeFourCC macro creates a 32 bit int with the letters "reversed". This -// ! is a leftover of the original Win-only code. It happens to work OK on little endian machines, -// ! when stored in memory the letters are in the expected order. To be safe, always use the XMP -// ! endian control macros when storing to memory or loading from memory. - -// FourCC codes for the RIFF chunks -#define wavWaveTitleChunk MakeFourCC('D','I','S','P') -#define wavInfoCreateDateChunk MakeFourCC('I','C','R','D') -#define wavInfoArtistChunk MakeFourCC('I','A','R','T') -#define wavInfoAlbumChunk MakeFourCC('I','N','A','M') -#define wavInfoGenreChunk MakeFourCC('I','G','N','R') -#define wavInfoCommentChunk MakeFourCC('I','C','M','T') -#define wavInfoEngineerChunk MakeFourCC('I','E','N','G') -#define wavInfoCopyrightChunk MakeFourCC('I','C','O','P') -#define wavInfoSoftwareChunk MakeFourCC('I','S','F','T') - -#define wavInfoTag MakeFourCC('I','N','F','O') -#define wavWaveTag MakeFourCC('W','A','V','E') - -// DC -#define kTitle "title" -#define kCopyright "rights" - -// XMP -#define kCreateDate "CreateDate" - -// DM -#define kArtist "artist" -#define kAlbum "album" -#define kGenre "genre" -#define kLogComment "logComment" -#define kEngineer "engineer" -#define kSoftware "CreatorTool" - -// ------------------------------------------------------------------------------------------------- -// Legacy digest info -// ------------------ -// -// The original WAV handler code didn't keep a legacy digest, it imported the legacy on every open. -// Because local encoding is used for the legacy, this can cause loss in the XMP. (The use of local -// encoding itself is an issue, the AVI handler is using UTF-8.) -// -// The legacy digest for WAV is a list of chunk IDs and a 128-bit MD5 digest, formatted like: -// DISP,IART,ICMT,ICOP,ICRD,IENG,IGNR,INAM,ISFT;012345678ABCDEF012345678ABCDEF -// -// The list of IDs are the recognized legacy chunks, in alphabetical order. This the full list that -// could be recognized, not restricted to those actually present in the specific file. When opening -// a file the new software's list is used to create the comparison digest, not the list from the -// file. So that changes to the recognized list will trigger an import. -// -// The MD5 digest is computed from the full legacy chunk, including the ID and length portions, not -// including any pad byte for odd data length. The length must be in file (little endian) order, -// not native machine order. The legacy chunks are added to the digest in list (alphabetical by ID) -// order. -// -// Legacy can be imported in 3 circumstances: -// -// 1. If the file does not yet have XMP, all of the legacy is imported. -// -// 2. If the file has XMP and a digest, and the recomputed digest differs from the saved digest, the -// legacy is imported. The digest comparison is the full string, including the list of IDs. A -// check is made for each legacy item: -// 2a. If the legacy item is missing or has an empty value, the corresponding XMP is deleted. -// 2b. If the corresponding XMP is missing, the legacy value is imported. -// 2c. If the new legacy value differs from a local encoding of the XMP value, the legacy value -// is imported. -// -// 3. If the file has XMP but no digest, legacy is imported for items that have no corresponding XMP. -// Any existing XMP is left alone. This is protection for tools that might be XMP aware but do -// not provide a digest or any legacy reconciliation. - -#define TAG_MAX_SIZE 5024 - -// ================================================================================================= - -static inline int GetStringRiffSize ( const std::string & str ) -{ - int l = (int)strlen ( const_cast<char *> (str.data()) ); - if ( l & 1 ) ++l; - return l; -} - -// ================================================================================================= -/// \file WAV_Handler.cpp -/// \brief File format handler for WAV. -/// -/// This header ... -/// -// ================================================================================================= - -// ================================================================================================= -// WAV_MetaHandlerCTor -// =================== - -XMPFileHandler * WAV_MetaHandlerCTor ( XMPFiles * parent ) -{ - return new WAV_MetaHandler ( parent ); - -} // WAV_MetaHandlerCTor - -// ================================================================================================= -// WAV_CheckFormat -// =============== -// -// A WAVE file must begin with "RIFF", a 4 byte little endian length, then "WAVE". The length should -// be fileSize-8, but we don't bother checking this here. - -bool WAV_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef fileRef, - XMPFiles * parent ) -{ - IgnoreParam(format); IgnoreParam(parent); - XMP_Assert ( format == kXMP_WAVFile ); - - if ( fileRef == 0 ) return false; - - enum { kBufferSize = 12 }; - XMP_Uns8 buffer [kBufferSize]; - - LFA_Seek ( fileRef, 0, SEEK_SET ); - LFA_Read ( fileRef, buffer, kBufferSize ); - - // "RIFF" is 52 49 46 46, "WAVE" is 57 41 56 45 - if ( (! CheckBytes ( &buffer[0], "\x52\x49\x46\x46", 4 )) || - (! CheckBytes ( &buffer[8], "\x57\x41\x56\x45", 4 )) ) return false; - - return true; - -} // WAV_CheckFormat - -// ================================================================================================= -// WAV_MetaHandler::WAV_MetaHandler -// ================================ - -WAV_MetaHandler::WAV_MetaHandler ( XMPFiles * _parent ) -{ - this->parent = _parent; - this->handlerFlags = kWAV_HandlerFlags; - this->stdCharForm = kXMP_Char8Bit; - -} // WAV_MetaHandler::WAV_MetaHandler - -// ================================================================================================= -// WAV_MetaHandler::~WAV_MetaHandler -// ================================= - -WAV_MetaHandler::~WAV_MetaHandler() -{ - // Nothing to do. -} // WAV_MetaHandler::~WAV_MetaHandler - - - -// ================================================================================================= -// WAV_MetaHandler::UpdateFile -// =========================== - -void WAV_MetaHandler::UpdateFile ( bool doSafeUpdate ) -{ - if ( ! this->needsUpdate ) return; - if ( doSafeUpdate ) XMP_Throw ( "WAV_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); - - bool fReconciliate = ! (this->parent->openFlags & kXMPFiles_OpenOnlyXMP); - bool ok; - - std::string strTitle, strArtist, strComment, strCopyright, strCreateDate, - strEngineer, strGenre, strAlbum, strSoftware; - - if ( fReconciliate ) { - - // Get the legacy item values, create the new digest, and add the digest to the XMP. The - // legacy chunks in alphabetical ID order are: - // DISP - title - // IART - artist - // ICMT - log comment - // ICOP - copyright - // ICRD - create date - // IENG - engineer - // IGNR - genre - // INAM - album - // ISFT - software - - MD5_CTX md5Ctx; - std::string digestStr; - XMP_Uns8 md5Val[16]; - static char* hexDigits = "0123456789ABCDEF"; - - MD5Init ( &md5Ctx ); - - // Prepare the legacy values and compute the new digest. - - PrepareLegacyExport ( kXMP_NS_DC, kTitle, wavWaveTitleChunk, &strTitle, &digestStr, &md5Ctx, true /* LangAlt */ ); - PrepareLegacyExport ( kXMP_NS_DM, kArtist, wavInfoArtistChunk, &strArtist, &digestStr, &md5Ctx ); - PrepareLegacyExport ( kXMP_NS_DM, kLogComment, wavInfoCommentChunk, &strComment, &digestStr, &md5Ctx ); - PrepareLegacyExport ( kXMP_NS_DC, kCopyright, wavInfoCopyrightChunk, &strCopyright, &digestStr, &md5Ctx, true /* LangAlt */ ); - PrepareLegacyExport ( kXMP_NS_XMP, kCreateDate, wavInfoCreateDateChunk, &strCreateDate, &digestStr, &md5Ctx ); - PrepareLegacyExport ( kXMP_NS_DM, kEngineer, wavInfoEngineerChunk, &strEngineer, &digestStr, &md5Ctx ); - PrepareLegacyExport ( kXMP_NS_DM, kGenre, wavInfoGenreChunk, &strGenre, &digestStr, &md5Ctx ); - PrepareLegacyExport ( kXMP_NS_DM, kAlbum, wavInfoAlbumChunk, &strAlbum, &digestStr, &md5Ctx ); - PrepareLegacyExport ( kXMP_NS_XMP, kSoftware, wavInfoSoftwareChunk, &strSoftware, &digestStr, &md5Ctx ); - - // Finish the digest and add it to the XMP. - - MD5Final ( md5Val, &md5Ctx ); - - digestStr[digestStr.size()-1] = ';'; - for ( size_t i = 0; i < 16; ++i ) { - XMP_Uns8 byte = md5Val[i]; - digestStr += hexDigits [byte >> 4]; - digestStr += hexDigits [byte & 0xF]; - } - - XMP_StringLen oldLen = (XMP_StringLen)this->xmpPacket.size(); - this->xmpObj.SetProperty ( kXMP_NS_WAV, "NativeDigest", digestStr.c_str() ); - try { - this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength), - oldLen ); - } catch ( ... ) { - this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); - } - - } - - XMP_StringPtr packetStr = this->xmpPacket.c_str(); - XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); - if ( packetLen == 0 ) return; - - // Make sure we're writing an even number of bytes as required by the RIFF specification - if ( (this->xmpPacket.size() & 1) == 1 ) - this->xmpPacket.push_back (' '); - XMP_Assert ( (this->xmpPacket.size() & 1) == 0 ); - packetStr = this->xmpPacket.c_str(); // ! Make sure they are current. - packetLen = (XMP_StringLen)this->xmpPacket.size(); - - LFA_FileRef fileRef ( this->parent->fileRef ); - if ( fileRef == 0 ) return; - - RIFF_Support::RiffState riffState; - long numTags = RIFF_Support::OpenRIFF ( fileRef, riffState ); - if ( numTags == 0 ) return; - - ok = RIFF_Support::PutChunk ( fileRef, riffState, formtypeWAVE, kXMPUserDataType, (char*)packetStr, packetLen ); - if ( ! ok ) return; - - ok = CreatorAtom::Update ( this->xmpObj, fileRef, formtypeWAVE, riffState ); - if ( ! ok ) return; - - // If needed, reconciliate the XMP data back into the native metadata. - if ( fReconciliate ) { - - PutChunk ( fileRef, riffState, wavWaveTag, wavWaveTitleChunk, strTitle.c_str(), (XMP_Int32)strTitle.size() ); - - // Pad the old tags - RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, 0, wavInfoCreateDateChunk, 0 ); - RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, 0, wavInfoArtistChunk, 0 ); - RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, 0, wavInfoAlbumChunk, 0 ); - RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, 0, wavInfoGenreChunk, 0 ); - RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, 0, wavInfoCommentChunk, 0 ); - RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, 0, wavInfoEngineerChunk, 0 ); - RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, 0, wavInfoCopyrightChunk, 0 ); - RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, 0, wavInfoSoftwareChunk, 0 ); - - // Get the old INFO list - std::string strOldInfo; - unsigned long lOldSize = 0; - bool ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, FOURCC_LIST, wavWaveTag, wavInfoTag, 0, &lOldSize ); - if ( ok ) { - // We have to get rid of the "INFO" first. - strOldInfo.reserve ( lOldSize ); - strOldInfo.assign ( lOldSize, ' ' ); - RIFF_Support::GetRIFFChunk ( fileRef, riffState, FOURCC_LIST, wavWaveTag, wavInfoTag, (char*)strOldInfo.c_str(), &lOldSize ); - // lOldSize -= 4; - } - - // TODO: Cleaning up the OldInfo from the padding - - // Pad the old INFO list - RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, wavWaveTag, FOURCC_LIST, wavInfoTag ); - - // Calculating the new INFO list size - XMP_Int32 dwListSize = 4 + 8 * 8 + - GetStringRiffSize ( strCreateDate ) + - GetStringRiffSize ( strArtist ) + - GetStringRiffSize ( strAlbum ) + - GetStringRiffSize ( strGenre ) + - GetStringRiffSize ( strComment ) + - GetStringRiffSize ( strEngineer ) + - GetStringRiffSize ( strCopyright ) + - GetStringRiffSize ( strSoftware ) + - lOldSize; // list id (4 bytes) + 8 tags hdrs (8 each) - - ok = MakeChunk ( fileRef, riffState, formtypeWAVE, dwListSize + 8 ); - if ( ! ok ) return; // If there's an error making a chunk, bail - - // Building the INFO list header - RIFF_Support::ltag listtag; - listtag.id = MakeUns32LE ( FOURCC_LIST ); - listtag.len = MakeUns32LE ( dwListSize ); - listtag.subid = MakeUns32LE ( wavInfoTag ); - LFA_Write ( fileRef, &listtag, 12 ); - - // Writing all the chunks - RIFF_Support::WriteChunk ( fileRef, wavInfoCreateDateChunk, strCreateDate.c_str(), GetStringRiffSize ( strCreateDate ) ); - RIFF_Support::WriteChunk ( fileRef, wavInfoArtistChunk, strArtist.c_str(), GetStringRiffSize ( strArtist ) ); - RIFF_Support::WriteChunk ( fileRef, wavInfoAlbumChunk, strAlbum.c_str(), GetStringRiffSize ( strAlbum ) ); - RIFF_Support::WriteChunk ( fileRef, wavInfoGenreChunk, strGenre.c_str(), GetStringRiffSize ( strGenre ) ); - RIFF_Support::WriteChunk ( fileRef, wavInfoCommentChunk, strComment.c_str(), GetStringRiffSize ( strComment ) ); - RIFF_Support::WriteChunk ( fileRef, wavInfoEngineerChunk, strEngineer.c_str(), GetStringRiffSize ( strEngineer ) ); - RIFF_Support::WriteChunk ( fileRef, wavInfoCopyrightChunk, strCopyright.c_str(), GetStringRiffSize ( strCopyright ) ); - RIFF_Support::WriteChunk ( fileRef, wavInfoSoftwareChunk, strSoftware.c_str(), GetStringRiffSize ( strSoftware ) ); - - LFA_Write ( fileRef, strOldInfo.c_str(), lOldSize ); - - } - - this->needsUpdate = false; - -} // WAV_MetaHandler::UpdateFile - -// ================================================================================================= -// WAV_MetaHandler::WriteFile -// ========================== - -void WAV_MetaHandler::WriteFile ( LFA_FileRef sourceRef, - const std::string & sourcePath ) -{ - IgnoreParam(sourceRef); IgnoreParam(sourcePath); - - XMP_Throw ( "WAV_MetaHandler::WriteFile: Not supported", kXMPErr_Unavailable ); - -} // WAV_MetaHandler::WriteFile - -// ================================================================================================= - -static void AddDigestItem ( XMP_Uns32 legacyID, std::string & legacyStr, std::string * digestStr, MD5_CTX * md5 ) -{ - - XMP_Uns32 leID = MakeUns32LE ( legacyID ); - XMP_Uns32 leLen = MakeUns32LE ( (XMP_Uns32)legacyStr.size()); - - digestStr->append ( (char*)(&leID), 4 ); - digestStr->append ( "," ); - - MD5Update ( md5, (XMP_Uns8*)&leID, 4 ); - MD5Update ( md5, (XMP_Uns8*)&leLen, 4 ); - MD5Update ( md5, (XMP_Uns8*)legacyStr.c_str(), (XMP_Int32)legacyStr.size() ); - -} // AddDigestItem - -// ================================================================================================= - -static void AddCurrentDigestItem ( LFA_FileRef fileRef, RIFF_Support::RiffState riffState, - XMP_Uns32 tagID, XMP_Uns32 parentID, std::string * digestStr, MD5_CTX * md5 ) -{ - unsigned long legacySize; - std::string legacyStr; - - bool found = RIFF_Support::GetRIFFChunk ( fileRef, riffState, tagID, parentID, 0, 0, &legacySize ); - if ( found ) { - legacyStr.reserve ( legacySize ); - legacyStr.assign ( legacySize, ' ' ); - (void) RIFF_Support::GetRIFFChunk ( fileRef, riffState, tagID, parentID, 0, (char*)legacyStr.c_str(), &legacySize ); - } - - AddDigestItem ( tagID, legacyStr, digestStr, md5 ); - -} // AddCurrentDigestItem - -// ================================================================================================= - -static void CreateCurrentDigest ( LFA_FileRef fileRef, RIFF_Support::RiffState riffState, std::string * digestStr ) -{ - MD5_CTX md5Ctx; - XMP_Uns8 md5Val[16]; - static char* hexDigits = "0123456789ABCDEF"; - - MD5Init ( &md5Ctx ); - - AddCurrentDigestItem ( fileRef, riffState, wavWaveTitleChunk, wavWaveTag, digestStr, &md5Ctx ); - AddCurrentDigestItem ( fileRef, riffState, wavInfoArtistChunk, wavInfoTag, digestStr, &md5Ctx ); - AddCurrentDigestItem ( fileRef, riffState, wavInfoCommentChunk, wavInfoTag, digestStr, &md5Ctx ); - AddCurrentDigestItem ( fileRef, riffState, wavInfoCopyrightChunk, wavInfoTag, digestStr, &md5Ctx); - AddCurrentDigestItem ( fileRef, riffState, wavInfoCreateDateChunk, wavInfoTag, digestStr, &md5Ctx ); - AddCurrentDigestItem ( fileRef, riffState, wavInfoEngineerChunk, wavInfoTag, digestStr, &md5Ctx ); - AddCurrentDigestItem ( fileRef, riffState, wavInfoGenreChunk, wavInfoTag, digestStr, &md5Ctx ); - AddCurrentDigestItem ( fileRef, riffState, wavInfoAlbumChunk, wavInfoTag, digestStr, &md5Ctx ); - AddCurrentDigestItem ( fileRef, riffState, wavInfoSoftwareChunk, wavInfoTag, digestStr, &md5Ctx ); - - MD5Final ( md5Val, &md5Ctx ); - - (*digestStr)[digestStr->size()-1] = ';'; - for ( size_t i = 0; i < 16; ++i ) { - XMP_Uns8 byte = md5Val[i]; - (*digestStr) += hexDigits [byte >> 4]; - (*digestStr) += hexDigits [byte & 0xF]; - } - -} // CreateCurrentDigest - - -// ================================================================================================= -// WAV_MetaHandler::CacheFileData -// ============================== - -void WAV_MetaHandler::CacheFileData() -{ - - this->containsXMP = false; - bool fReconciliate = ! (this->parent->openFlags & kXMPFiles_OpenOnlyXMP); - bool keepExistingXMP = false; // By default an import will replace existing XMP. - bool haveLegacyItem, haveXMPItem; - - LFA_FileRef fileRef ( this->parent->fileRef ); //*** simplify to assignment - - bool updateFile = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); - if ( updateFile ) { - - // Workaround for bad files in the field that have a bad size in the outermost RIFF chunk. - // Repair the cases where the length is too long (beyond EOF). Don't repair a length that is - // less than EOF, we don't know if there actually are multiple top level chunks. There is - // also a check and "runtime repair" inside ReadTag, needed for read-only file access. - - XMP_Int64 fileLen = LFA_Measure ( fileRef ); - XMP_Uns32 riffLen; - - LFA_Seek ( fileRef, 4, SEEK_SET ); - LFA_Read ( fileRef, &riffLen, 4 ); - riffLen = GetUns32LE ( &riffLen ); - - if ( (fileLen >= 8) && ((XMP_Int64)riffLen > (fileLen - 8)) ) { // Is the initial chunk too long? - - bool repairFile = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenRepairFile ); - if ( ! repairFile ) { - XMP_Throw ( "Initial RIFF tag exceeds file length", kXMPErr_BadValue ); - } else { - riffLen = MakeUns32LE ( (XMP_Uns32)fileLen - 8 ); - LFA_Seek ( fileRef, 4, SEEK_SET ); - LFA_Write ( fileRef, &riffLen, 4 ); - } - - } - - } - - // Contnue with normal processing. - - RIFF_Support::RiffState riffState; - long numTags = RIFF_Support::OpenRIFF ( fileRef, riffState ); - if ( numTags == 0 ) return; //*** shouldn't we throw ? XMP_Throw("invalid file format") or such? - - // Determine the size of the metadata - unsigned long bufferSize(0); - haveLegacyItem = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType /* _PMX, the xmp packet */, 0, 0, 0, &bufferSize ); - - if ( ! haveLegacyItem ) { - - packetInfo.writeable = true; // If no packet found, created packets will be writeable - - } else if ( bufferSize > 0 ) { - - // Size and clear the buffer - this->xmpPacket.reserve(bufferSize); - this->xmpPacket.assign(bufferSize, ' '); - - // Get the metadata - XMP_Uns64 xmpPacketPosition; - haveLegacyItem = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType /* _PMX, the xmp packet */, 0, 0, - const_cast<char *>(this->xmpPacket.data()), &bufferSize, &xmpPacketPosition ); - if ( haveLegacyItem ) { - this->packetInfo.offset = xmpPacketPosition; - this->packetInfo.length = bufferSize; - this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); - this->containsXMP = true; - } - - } - - // Figure out what to do overall. If there is no XMP, everything gets imported. If there is XMP - // but no digest, only import if there is no corresponding XMP. If there is XMP and a digest - // that matches, nothing is imported. If there is XMP and a digest that does not match, import - // everything that provides "new info". - - if ( fReconciliate && this->containsXMP ) { - - std::string savedDigest; - haveXMPItem = this->xmpObj.GetProperty ( kXMP_NS_WAV, "NativeDigest", &savedDigest, 0 ); - - if ( ! haveXMPItem ) { - keepExistingXMP = true; - } else { - std::string currDigest; - CreateCurrentDigest ( fileRef, riffState, &currDigest ); - if ( currDigest == savedDigest ) fReconciliate = false; - } - - } - - // Now import the individual legacy items. - - if ( fReconciliate ) { - - ImportLegacyItem ( riffState, wavWaveTitleChunk, wavWaveTag, kXMP_NS_DC, kTitle, keepExistingXMP, true /* LangAlt */ ); - ImportLegacyItem ( riffState, wavInfoCreateDateChunk, wavInfoTag, kXMP_NS_XMP, kCreateDate, keepExistingXMP ); - ImportLegacyItem ( riffState, wavInfoArtistChunk, wavInfoTag, kXMP_NS_DM, kArtist, keepExistingXMP ); - ImportLegacyItem ( riffState, wavInfoAlbumChunk, wavInfoTag, kXMP_NS_DM, kAlbum, keepExistingXMP ); - ImportLegacyItem ( riffState, wavInfoGenreChunk, wavInfoTag, kXMP_NS_DM, kGenre, keepExistingXMP ); - ImportLegacyItem ( riffState, wavInfoCommentChunk, wavInfoTag, kXMP_NS_DM, kLogComment, keepExistingXMP ); - ImportLegacyItem ( riffState, wavInfoEngineerChunk, wavInfoTag, kXMP_NS_DM, kEngineer, keepExistingXMP ); - ImportLegacyItem ( riffState, wavInfoCopyrightChunk, wavInfoTag, kXMP_NS_DC, kCopyright, keepExistingXMP, true /* LangAlt */ ); - ImportLegacyItem ( riffState, wavInfoSoftwareChunk, wavInfoTag, kXMP_NS_XMP, kSoftware, keepExistingXMP ); - - } - - CreatorAtom::Import ( this->xmpObj, fileRef, riffState ); - - // Update the xmpPacket, as the xmpObj might have been updated with legacy info - if ( this->packetInfo.length == kXMPFiles_UnknownLength ) - { - // kXMPFiles_UnknownLength <=> no prior packet, thus do not attempt - // to put in place (creates several seconds of delay - this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat ) ); - } - else - { - try { - this->xmpObj.SerializeToBuffer ( &this->xmpPacket, - (kXMP_UseCompactFormat | kXMP_ExactPacketLength) , this->packetInfo.length ); - } catch ( XMP_Error ) { - this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat ) ); - } - } - - this->processedXMP = this->containsXMP; - -} // WAV_MetaHandler::CacheFileData - -// ================================================================================================= - -void WAV_MetaHandler::UTF8ToMBCS ( std::string * inoutStr ) -{ - std::string localStr; - - try { - ReconcileUtils::UTF8ToLocal ( inoutStr->c_str(), inoutStr->size(), &localStr ); - inoutStr->swap ( localStr ); - } catch ( ... ) { - inoutStr->erase(); - } - -} - -// ================================================================================================= - -void WAV_MetaHandler::MBCSToUTF8 ( std::string * inoutStr ) -{ - std::string utf8Str; - - try { - ReconcileUtils::LocalToUTF8 ( inoutStr->c_str(), inoutStr->size(), &utf8Str ); - inoutStr->swap ( utf8Str ); - } catch ( ... ) { - inoutStr->erase(); - } - -} - -// ================================================================================================= - -void WAV_MetaHandler::PrepareLegacyExport ( XMP_StringPtr xmpNS, XMP_StringPtr xmpProp, XMP_Uns32 legacyID, std::string * legacyStr, - std::string * digestStr, MD5_CTX * md5, bool langAlt /* = false */ ) -{ - if ( ! langAlt ) { - this->xmpObj.GetProperty ( xmpNS, xmpProp, legacyStr, 0 ); - } else { - this->xmpObj.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, legacyStr, 0 ); - } - UTF8ToMBCS ( legacyStr ); // Convert the XMP value to local encoding. - - // ! Add a 0 pad byte if the value has an odd length. This would be better done inside RIFF_Support, - // ! but that means changing too much at this moment. - - if ( (legacyStr->size() & 1) == 1 ) { - (*legacyStr) += " "; - (*legacyStr)[legacyStr->size()-1] = 0; - } - - if ( legacyID == wavWaveTitleChunk ) { // ! The title gets 32 bit LE type code of 1 inserted. - legacyStr->insert ( 0, "1234" ); - PutUns32LE ( 1, (void*)legacyStr->c_str() ); - } - - AddDigestItem ( legacyID, *legacyStr, digestStr, md5 ); - -} // WAV_MetaHandler::PrepareLegacyExport - -// ================================================================================================= - -void WAV_MetaHandler::ImportLegacyItem ( RIFF_Support::RiffState & inOutRiffState, - XMP_Uns32 tagID, - XMP_Uns32 parentID, - XMP_StringPtr xmpNS, - XMP_StringPtr xmpProp, - bool keepExistingXMP, - bool langAlt /* = false */ ) -{ - LFA_FileRef fileRef ( this->parent->fileRef ); - - bool haveLegacyItem, haveXMPItem; - std::string legacyStr, xmpStr; - unsigned long legacySize; - - if ( ! langAlt ) { - haveXMPItem = this->xmpObj.GetProperty ( xmpNS, xmpProp, &xmpStr, 0 ); - } else { - haveXMPItem = this->xmpObj.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &xmpStr, 0 ); - } - - haveLegacyItem = RIFF_Support::GetRIFFChunk ( fileRef, inOutRiffState, tagID, parentID, 0, 0, &legacySize ); - if ( (legacySize == 0) || ((tagID == wavWaveTitleChunk) && (legacySize <= 4)) ) haveLegacyItem = false; - if ( haveXMPItem && keepExistingXMP ) haveLegacyItem = false; // Simplify following checks. - - if ( ! haveLegacyItem ) { - - // No legacy item, delete the corresponding XMP if we're not keeping existing XMP. - - if ( haveXMPItem && (! keepExistingXMP) ) { - if ( ! langAlt ) { - this->xmpObj.DeleteProperty ( xmpNS, xmpProp ); - } else { - std::string xdPath; - SXMPUtils::ComposeLangSelector ( xmpNS, xmpProp, "x-default", &xdPath ); - this->xmpObj.DeleteProperty ( xmpNS, xdPath.c_str() ); - if ( this->xmpObj.CountArrayItems ( xmpNS, xmpProp ) == 0 ) this->xmpObj.DeleteProperty ( xmpNS, xmpProp ); - } - } - - } else { - - // Have a legacy Item, update the XMP as appropriate. - - XMP_Assert ( (! haveXMPItem) || (! keepExistingXMP) ); - - legacyStr.reserve ( legacySize ); - legacyStr.assign ( legacySize, ' ' ); - haveLegacyItem = RIFF_Support::GetRIFFChunk ( fileRef, inOutRiffState, tagID, parentID, 0, (char*)legacyStr.c_str(), &legacySize ); - XMP_Assert ( haveLegacyItem ); - - if ( tagID == wavWaveTitleChunk ) { // Check and strip the type code from the title. - XMP_Assert ( legacySize > 4 ); - XMP_Uns32 typeCode = GetUns32LE ( legacyStr.data() ); - if( typeCode != 1 ) return; // Bail if the type code isn't 1. - legacyStr.erase ( 0, 4 ); // Reduce the value to just the text part. - } - - if ( haveXMPItem ) { - // Don't update the XMP if the legacy value matches the localized XMP value. - UTF8ToMBCS ( &xmpStr ); - if ( xmpStr == legacyStr ) return; - } - - MBCSToUTF8 ( &legacyStr ); - if ( ! langAlt ) { - this->xmpObj.SetProperty ( xmpNS, xmpProp, legacyStr.c_str(), 0 ); - } else { - this->xmpObj.SetLocalizedText ( xmpNS, xmpProp, "", "x-default", legacyStr.c_str(), 0 ); - } - this->containsXMP = true; - - } - -} // WAV_MetaHandler::LoadPropertyFromRIFF - -// ================================================================================================= - -#endif // XMP_UNIXBuild diff --git a/source/XMPFiles/FileHandlers/WAV_Handler.hpp b/source/XMPFiles/FileHandlers/WAV_Handler.hpp deleted file mode 100644 index 4bd9aae..0000000 --- a/source/XMPFiles/FileHandlers/WAV_Handler.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef __WAV_Handler_hpp__ -#define __WAV_Handler_hpp__ 1 - -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -#include "XMP_Environment.h" // ! This must be the first include. -#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed. - -#include "XMPFiles_Impl.hpp" - -#include "MD5.h" - -// ================================================================================================= -/// \file WAV_Handler.hpp -/// \brief File format handler for WAV. -/// -/// This header ... -/// -// ================================================================================================= - -namespace RIFF_Support -{ - class RiffState; -} - -extern XMPFileHandler * WAV_MetaHandlerCTor ( XMPFiles * parent ); - -extern bool WAV_CheckFormat ( XMP_FileFormat format, - XMP_StringPtr filePath, - LFA_FileRef fileRef, - XMPFiles * parent ); - -static const XMP_OptionBits kWAV_HandlerFlags = ( kXMPFiles_CanInjectXMP | - kXMPFiles_CanExpand | - kXMPFiles_PrefersInPlace | - kXMPFiles_AllowsOnlyXMP | - kXMPFiles_ReturnsRawPacket ); - // In the future, we'll add kXMPFiles_CanReconcile - -class WAV_MetaHandler : public XMPFileHandler -{ -public: - - WAV_MetaHandler ( XMPFiles * parent ); - ~WAV_MetaHandler(); - - void CacheFileData(); - - void UpdateFile ( bool doSafeUpdate ); - void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); - -private: - - void ImportLegacyItem ( RIFF_Support::RiffState& inOutRiffState, XMP_Uns32 tagID, XMP_Uns32 parentID, - XMP_StringPtr xmpNS, XMP_StringPtr xmpProp, bool keepExistingXMP, bool langAlt = false ); - - void PrepareLegacyExport ( XMP_StringPtr xmpNS, XMP_StringPtr xmpProp, XMP_Uns32 legacyID, std::string * legacyStr, - std::string * digestStr, MD5_CTX * md5, bool langAlt = false ); - - void UTF8ToMBCS ( std::string * str ); - void MBCSToUTF8 ( std::string * str ); - -}; // WAV_MetaHandler - -// ================================================================================================= - -#endif // XMP_UNIXBuild -#endif // __WAV_Handler_hpp__ diff --git a/source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp index 47c0851..009ced0 100644 --- a/source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp +++ b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -136,9 +136,9 @@ bool XDCAMEX_CheckFormat ( XMP_FileFormat format, tempPath += kDirChar; tempPath += clipName; size_t pathLen = tempPath.size() + 1; // Include a terminating nul. - parent->handlerTemp = malloc ( pathLen ); - if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory ); - memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen ); + parent->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); return true; @@ -164,13 +164,13 @@ XDCAMEX_MetaHandler::XDCAMEX_MetaHandler ( XMPFiles * _parent ) : expat(0) this->handlerFlags = kXDCAMEX_HandlerFlags; this->stdCharForm = kXMP_Char8Bit; - // Extract the root path and clip name from handlerTemp. + // Extract the root path and clip name from tempPtr. - XMP_Assert ( this->parent->handlerTemp != 0 ); + XMP_Assert ( this->parent->tempPtr != 0 ); - this->rootPath.assign ( (char*) this->parent->handlerTemp ); - free ( this->parent->handlerTemp ); - this->parent->handlerTemp = 0; + this->rootPath.assign ( (char*) this->parent->tempPtr ); + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; SplitLeafName ( &this->rootPath, &this->clipName ); @@ -184,9 +184,9 @@ XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler() { this->CleanupLegacyXML(); - if ( this->parent->handlerTemp != 0 ) { - free ( this->parent->handlerTemp ); - this->parent->handlerTemp = 0; + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; } } // XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler @@ -272,11 +272,6 @@ void XDCAMEX_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) void XDCAMEX_MetaHandler::CleanupLegacyXML() { - - if ( ! this->defaultNS.empty() ) { - SXMPMeta::DeleteNamespace ( this->defaultNS.c_str() ); - this->defaultNS.erase(); - } if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; } @@ -290,7 +285,7 @@ void XDCAMEX_MetaHandler::CleanupLegacyXML() void XDCAMEX_MetaHandler::CacheFileData() { - XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + XMP_Assert ( ! this->containsXMP ); // See if the clip's .XMP file exists. @@ -381,7 +376,7 @@ void XDCAMEX_MetaHandler::GetTakeDuration ( const std::string & takeURI, std::st takeXMLFile.fileRef = LFA_Open ( takePath.c_str(), 'r' ); if ( takeXMLFile.fileRef == 0 ) return; // The open failed. - ExpatAdapter * expat = XMP_NewExpatAdapter(); + ExpatAdapter * expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); if ( this->expat == 0 ) return; XMP_Uns8 buffer [64*1024]; @@ -459,7 +454,7 @@ void XDCAMEX_MetaHandler::GetTakeUMID ( const std::string& clipUMID, mediaproXMLFile.fileRef = LFA_Open ( mediapropath.c_str(), 'r' ); if ( mediaproXMLFile.fileRef == 0 ) return; // The open failed. - ExpatAdapter * expat = XMP_NewExpatAdapter(); + ExpatAdapter * expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); if ( this->expat == 0 ) return; XMP_Uns8 buffer [64*1024]; @@ -562,15 +557,11 @@ void XDCAMEX_MetaHandler::ProcessXMP() std::string xmlPath; this->MakeClipFilePath ( &xmlPath, "M01.XML" ); - // *** Hack: Special case trickery to detect and clean up default XML namespace usage. - - bool haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", 0 ); // Is there already a default namespace? - AutoFile xmlFile; xmlFile.fileRef = LFA_Open ( xmlPath.c_str(), 'r' ); if ( xmlFile.fileRef == 0 ) return; // The open failed. - this->expat = XMP_NewExpatAdapter(); + this->expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); if ( this->expat == 0 ) XMP_Throw ( "XDCAMEX_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory ); XMP_Uns8 buffer [64*1024]; @@ -584,12 +575,6 @@ void XDCAMEX_MetaHandler::ProcessXMP() LFA_Close ( xmlFile.fileRef ); xmlFile.fileRef = 0; - if ( ! haveDefaultNS ) { - // No prior default XML namespace. If there is one now, remember it and delete it when done. - haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", &this->defaultNS ); - XMP_Assert ( haveDefaultNS == (! this->defaultNS.empty()) ); - } - // The root element should be NonRealTimeMeta in some namespace. Take whatever this file uses. XML_Node & xmlTree = this->expat->tree; diff --git a/source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp index 63852b5..ee7b23f 100644 --- a/source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp +++ b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -69,7 +69,7 @@ private: void CleanupLegacyXML(); - std::string rootPath, clipName, defaultNS, xdcNS, legacyNS, clipUMID; + std::string rootPath, clipName, xdcNS, legacyNS, clipUMID; ExpatAdapter * expat; XML_Node * clipMetadata; diff --git a/source/XMPFiles/FileHandlers/XDCAM_Handler.cpp b/source/XMPFiles/FileHandlers/XDCAM_Handler.cpp index 6ccfcab..d88109e 100644 --- a/source/XMPFiles/FileHandlers/XDCAM_Handler.cpp +++ b/source/XMPFiles/FileHandlers/XDCAM_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -21,25 +21,25 @@ 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: +/// A typical FAM layout looks like (note mixed case for General, Clip, Edit, and Sub folders): /// /// .../MyMovie/ /// INDEX.XML /// DISCMETA.XML /// MEDIAPRO.XML -/// GENERAL/ +/// General/ /// unknown files -/// CLIP/ +/// Clip/ /// C0001.MXF /// C0001M01.XML /// C0001M01.XMP /// C0002.MXF /// C0002M01.XML /// C0002M01.XMP -/// SUB/ +/// Sub/ /// C0001S01.MXF /// C0002S01.MXF -/// EDIT/ +/// Edit/ /// E0001E01.SMI /// E0001M01.XML /// E0002E01.SMI @@ -112,10 +112,10 @@ using namespace std; // parentName - empty // leafName - "C0001" // -// If the client passed a FAM file path, like ".../MyMovie/EDIT/E0001E01.SMI", they are: +// If the client passed a FAM file path, like ".../MyMovie/Edit/E0001E01.SMI", they are: // rootPath - "..." // gpName - "MyMovie" -// parentName - "EDIT" +// parentName - "EDIT" (common code has shifted the case) // leafName - "E0001E01" // // If the client passed a SAM file path, like ".../MyMovie/PROAV/CLPR/C0001/C0001A02.MXF", they are: @@ -134,7 +134,7 @@ using namespace std; // ! folder, we will fail to do the leading 'E' to 'C' coercion. We might also erroneously remove a // ! suffix from a mapped essence file with a name like ClipX01.MXF. -// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has +// ! The common code has shifted the gpName, parentName, and leafName strings to uppercase. It has // ! also made sure that for a logical clip path the rootPath is an existing folder, and that the // ! file exists for a full file path. @@ -169,6 +169,7 @@ bool XDCAM_CheckFormat ( XMP_FileFormat format, // This is the existing file case. See if this is FAM or SAM, tweak the clip name as needed. if ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) { + // ! The standard says Clip/Edit/Sub, but the caller has already shifted to upper case. isFAM = true; } else if ( (gpName != "CLPR") && (gpName != "EDTR") ) { return false; @@ -215,7 +216,7 @@ bool XDCAM_CheckFormat ( XMP_FileFormat format, // Make sure the general XDCAM package structure is legit. Set tempPath as a bogus path of the // form <root>/<FAM-or-SAM>/<clip>, e.g. ".../MyMovie/FAM/C0001". This is passed the handler via - // the handlerTemp hackery. + // the tempPtr hackery. if ( isFAM ) { @@ -228,7 +229,7 @@ bool XDCAM_CheckFormat ( XMP_FileFormat format, if ( GetChildMode ( tempPath, "MEDIAPRO.XML" ) != kFMode_IsFile ) return false; tempPath += kDirChar; - tempPath += "CLIP"; + tempPath += "Clip"; // ! Yes, mixed case. tempPath += kDirChar; tempPath += clipName; tempPath += "M01.XML"; @@ -278,9 +279,9 @@ bool XDCAM_CheckFormat ( XMP_FileFormat format, // from here to there. size_t pathLen = tempPath.size() + 1; // Include a terminating nul. - parent->handlerTemp = malloc ( pathLen ); - if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for XDCAM clip info", kXMPErr_NoMemory ); - memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. + parent->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for XDCAM clip info", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. return true; @@ -307,13 +308,13 @@ XDCAM_MetaHandler::XDCAM_MetaHandler ( XMPFiles * _parent ) : isFAM(false), expa this->handlerFlags = kXDCAM_HandlerFlags; this->stdCharForm = kXMP_Char8Bit; - // Extract the root path, clip name, and FAM/SAM flag from handlerTemp. + // Extract the root path, clip name, and FAM/SAM flag from tempPtr. - XMP_Assert ( this->parent->handlerTemp != 0 ); + XMP_Assert ( this->parent->tempPtr != 0 ); - this->rootPath.assign ( (char*) this->parent->handlerTemp ); - free ( this->parent->handlerTemp ); - this->parent->handlerTemp = 0; + this->rootPath.assign ( (char*) this->parent->tempPtr ); + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; SplitLeafName ( &this->rootPath, &this->clipName ); @@ -333,9 +334,9 @@ XDCAM_MetaHandler::~XDCAM_MetaHandler() { this->CleanupLegacyXML(); - if ( this->parent->handlerTemp != 0 ) { - free ( this->parent->handlerTemp ); - this->parent->handlerTemp = 0; + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; } } // XDCAM_MetaHandler::~XDCAM_MetaHandler @@ -351,7 +352,7 @@ void XDCAM_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suf *path += kDirChar; if ( this->isFAM ) { - *path += "CLIP"; + *path += "Clip"; // ! Yes, mixed case. } else { *path += "PROAV"; *path += kDirChar; @@ -427,11 +428,6 @@ void XDCAM_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) void XDCAM_MetaHandler::CleanupLegacyXML() { - - if ( ! this->defaultNS.empty() ) { - SXMPMeta::DeleteNamespace ( this->defaultNS.c_str() ); - this->defaultNS.erase(); - } if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; } @@ -445,7 +441,7 @@ void XDCAM_MetaHandler::CleanupLegacyXML() void XDCAM_MetaHandler::CacheFileData() { - XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + XMP_Assert ( ! this->containsXMP ); // See if the clip's .XMP file exists. @@ -515,17 +511,11 @@ void XDCAM_MetaHandler::ProcessXMP() std::string xmlPath, umid; this->MakeClipFilePath ( &xmlPath, "M01.XML" ); - // -------------------------------------------------------------- - // *** This is a minimal Q&D example of legacy metadata handling. - // *** Hack: Special case trickery to detect and clean up default XML namespace usage. - - bool haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", 0 ); // Is there already a default namespace? - AutoFile xmlFile; xmlFile.fileRef = LFA_Open ( xmlPath.c_str(), 'r' ); if ( xmlFile.fileRef == 0 ) return; // The open failed. - this->expat = XMP_NewExpatAdapter(); + 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]; @@ -539,12 +529,6 @@ void XDCAM_MetaHandler::ProcessXMP() LFA_Close ( xmlFile.fileRef ); xmlFile.fileRef = 0; - if ( ! haveDefaultNS ) { - // No prior default XML namespace. If there is one now, remember it and delete it when done. - haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", &this->defaultNS ); - XMP_Assert ( haveDefaultNS == (! this->defaultNS.empty()) ); - } - // The root element should be NonRealTimeMeta in some namespace. Take whatever this file uses. XML_Node & xmlTree = this->expat->tree; diff --git a/source/XMPFiles/FileHandlers/XDCAM_Handler.hpp b/source/XMPFiles/FileHandlers/XDCAM_Handler.hpp index abd861b..55f61dc 100644 --- a/source/XMPFiles/FileHandlers/XDCAM_Handler.hpp +++ b/source/XMPFiles/FileHandlers/XDCAM_Handler.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -68,7 +68,7 @@ private: void MakeLegacyDigest ( std::string * digestStr ); void CleanupLegacyXML(); - std::string rootPath, clipName, defaultNS, xdcNS, legacyNS; + std::string rootPath, clipName, xdcNS, legacyNS; bool isFAM; diff --git a/source/XMPFiles/FormatSupport/ASF_Support.cpp b/source/XMPFiles/FormatSupport/ASF_Support.cpp index 3baa7d0..1180f9d 100644 --- a/source/XMPFiles/FormatSupport/ASF_Support.cpp +++ b/source/XMPFiles/FormatSupport/ASF_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -1120,26 +1120,27 @@ void ASF_LegacyManager::ImportLegacy ( SXMPMeta* xmp ) if ( ! broadcastSet ) { ConvertMSDateToISODate ( fields[fieldCreationDate], &utf8 ); - xmp->SetProperty ( kXMP_NS_XMP, "CreateDate", utf8.c_str(), kXMP_DeleteExisting ); + if ( ! utf8.empty() ) xmp->SetProperty ( kXMP_NS_XMP, "CreateDate", utf8.c_str(), kXMP_DeleteExisting ); } FromUTF16 ( (UTF16Unit*)fields[fieldTitle].c_str(), (fields[fieldTitle].size() / 2), &utf8, false ); - xmp->SetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); + if ( ! utf8.empty() ) xmp->SetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); xmp->DeleteProperty ( kXMP_NS_DC, "creator" ); FromUTF16 ( (UTF16Unit*)fields[fieldAuthor].c_str(), (fields[fieldAuthor].size() / 2), &utf8, false ); - SXMPUtils::SeparateArrayItems ( xmp, kXMP_NS_DC, "creator", kXMPUtil_AllowCommas, utf8.c_str() ); + if ( ! utf8.empty() ) SXMPUtils::SeparateArrayItems ( xmp, kXMP_NS_DC, "creator", + (kXMP_PropArrayIsOrdered | kXMPUtil_AllowCommas), utf8.c_str() ); FromUTF16 ( (UTF16Unit*)fields[fieldCopyright].c_str(), (fields[fieldCopyright].size() / 2), &utf8, false ); - xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); + if ( ! utf8.empty() ) xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); FromUTF16 ( (UTF16Unit*)fields[fieldDescription].c_str(), (fields[fieldDescription].size() / 2), &utf8, false ); - xmp->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); + if ( ! utf8.empty() ) xmp->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); - xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", fields[fieldCopyrightURL].c_str(), kXMP_DeleteExisting ); + if ( ! utf8.empty() ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", fields[fieldCopyrightURL].c_str(), kXMP_DeleteExisting ); #if ! Exclude_LicenseURL_Recon - xmp->SetProperty ( kXMP_NS_XMP_Rights, "Certificate", fields[fieldLicenseURL].c_str(), kXMP_DeleteExisting ); + if ( ! fields[fieldLicenseURL].empty() ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "Certificate", fields[fieldLicenseURL].c_str(), kXMP_DeleteExisting ); #endif imported = true; @@ -1393,6 +1394,7 @@ void ASF_LegacyManager::ConvertMSDateToISODate ( std::string& source, std::strin date.second = second; date.nanoSecond = nanoSec; + date.hasTimeZone = true; // ! Needed for ConvertToUTCTime to do anything. SXMPUtils::ConvertToUTCTime ( &date ); // Normalize the date/time. SXMPUtils::ConvertFromDate ( date, dest ); // Convert to an ISO 8601 string. diff --git a/source/XMPFiles/FormatSupport/ASF_Support.hpp b/source/XMPFiles/FormatSupport/ASF_Support.hpp index 5260cb3..e170cb2 100644 --- a/source/XMPFiles/FormatSupport/ASF_Support.hpp +++ b/source/XMPFiles/FormatSupport/ASF_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/ID3_Support.cpp b/source/XMPFiles/FormatSupport/ID3_Support.cpp deleted file mode 100644 index ee8c008..0000000 --- a/source/XMPFiles/FormatSupport/ID3_Support.cpp +++ /dev/null @@ -1,1156 +0,0 @@ -// ================================================================================================= -// ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated -// All Rights Reserved -// -// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms -// of the Adobe license agreement accompanying it. -// ================================================================================================= - -#include "XMP_Environment.h" // ! This must be the first include. -#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed. - -#include "XMP_Const.h" - -#include "ID3_Support.hpp" - -#include "UnicodeConversions.hpp" -#include "Reconcile_Impl.hpp" - -#include <stdio.h> - -#if XMP_WinBuild - #pragma warning ( disable : 4996 ) // '...' was declared deprecated -#endif - -// For more information about the id3v2 specification -// Please refer to http://www.id3.org/develop.html - -namespace ID3_Support { - - char Genres[128][32]={ - "Blues", // 0 - "Classic Rock", // 1 - "Country", // 2 - "Dance", - "Disco", - "Funk", - "Grunge", - "Hip-Hop", - "Jazz", // 8 - "Metal", - "New Age", // 10 - "Oldies", - "Other", // 12 - "Pop", - "R&B", - "Rap", - "Reggae", // 16 - "Rock", // 17 - "Techno", - "Industrial", - "Alternative", - "Ska", - "Death Metal", - "Pranks", - "Soundtrack", // 24 - "Euro-Techno", - "Ambient", - "Trip-Hop", - "Vocal", - "Jazz+Funk", - "Fusion", - "Trance", - "Classical", // 32 - "Instrumental", - "Acid", - "House", - "Game", - "Sound Clip", - "Gospel", - "Noise", - "AlternRock", - "Bass", - "Soul", //42 - "Punk", - "Space", - "Meditative", - "Instrumental Pop", - "Instrumental Rock", - "Ethnic", - "Gothic", - "Darkwave", - "Techno-Industrial", - "Electronic", - "Pop-Folk", - "Eurodance", - "Dream", - "Southern Rock", - "Comedy", - "Cult", - "Gangsta", - "Top 40", - "Christian Rap", - "Pop/Funk", - "Jungle", - "Native American", - "Cabaret", - "New Wave", // 66 - "Psychadelic", - "Rave", - "Showtunes", - "Trailer", - "Lo-Fi", - "Tribal", - "Acid Punk", - "Acid Jazz", - "Polka", - "Retro", - "Musical", - "Rock & Roll", - "Hard Rock", - "Folk", // 80 - "Folk-Rock", - "National Folk", - "Swing", - "Fast Fusion", - "Bebob", - "Latin", - "Revival", - "Celtic", - "Bluegrass", // 89 - "Avantgarde", - "Gothic Rock", - "Progressive Rock", - "Psychedelic Rock", - "Symphonic Rock", - "Slow Rock", - "Big Band", - "Chorus", - "Easy Listening", - "Acoustic", - "Humour", // 100 - "Speech", - "Chanson", - "Opera", - "Chamber Music", - "Sonata", - "Symphony", - "Booty Bass", - "Primus", - "Porn Groove", - "Satire", - "Slow Jam", - "Club", - "Tango", - "Samba", - "Folklore", - "Ballad", - "Power Ballad", - "Rhythmic Soul", - "Freestyle", - "Duet", - "Punk Rock", - "Drum Solo", - "A capella", - "Euro-House", - "Dance Hall", - "Unknown" // 126 - }; - - // Some types - #ifndef XMP_Int64 - typedef unsigned long long XMP_Int64; - #endif - - static bool FindXMPFrame(LFA_FileRef inFileRef, XMP_Int64 &posXMP, XMP_Int64 &posPAD, unsigned long &dwExtendedTag, unsigned long &dwLen); - static unsigned long SkipExtendedHeader(LFA_FileRef inFileRef, XMP_Uns8 bVersion, XMP_Uns8 flag); - static bool GetFrameInfo(LFA_FileRef inFileRef, XMP_Uns8 bVersion, char *strFrameID, XMP_Uns8 &cflag1, XMP_Uns8 &cflag2, unsigned long &dwSize); - static bool ReadSize(LFA_FileRef inFileRef, XMP_Uns8 bVersion, unsigned long &dwSize); - static unsigned long CalculateSize(XMP_Uns8 bVersion, unsigned long dwSizeIn); - static bool LoadTagHeaderAndUnknownFrames(LFA_FileRef inFileRef, char *strBuffer, size_t strBufferLen, bool fRecon, unsigned long &posPad); - - #define GetFilePosition(file) LFA_Seek ( file, 0, SEEK_CUR ) - - const unsigned long k_dwTagHeaderSize = 10; - const unsigned long k_dwFrameHeaderSize = 10; - const unsigned long k_dwXMPLabelSize = 4; // 4 (size of "XMP\0") - -// ID3v2 flags %abcd0000 -// Where: -// a - Unsynchronisation -// b - Extended header -// c - Experimental indicator -// d - Footer present - const unsigned char flagUnsync = 0x80; // (MSb) - const unsigned char flagExt = 0x40; - const unsigned char flagExp = 0x20; - const unsigned char flagFooter = 0x10; - - -#ifndef Trace_ID3_Support - #define Trace_ID3_Support 0 -#endif - -// ================================================================================================= - -#if XMP_WinBuild - - #define stricmp _stricmp - -#else - - static int stricmp ( const char * left, const char * right ) // Case insensitive ASCII compare. - { - char chL = *left; // ! Allow for 0 passes in the loop (one string is empty). - char chR = *right; // ! Return -1 for stricmp ( "a", "Z" ). - - for ( ; (*left != 0) && (*right != 0); ++left, ++right ) { - chL = *left; - chR = *right; - if ( chL == chR ) continue; - if ( ('A' <= chL) && (chL <= 'Z') ) chL |= 0x20; - if ( ('A' <= chR) && (chR <= 'Z') ) chR |= 0x20; - if ( chL != chR ) break; - } - - if ( chL == chR ) return 0; - if ( chL < chR ) return -1; - return 1; - - } - -#endif - -// ================================================================================================= - -// *** Load Scenario: -// - Check for id3v2 tag -// -// - Parse through the frame for "PRIV" + UTF8 Encoding + "XMP" -// -// - If found, load it. -bool GetMetaData ( LFA_FileRef inFileRef, char* buffer, unsigned long* pBufferSize, ::XMP_Int64* fileOffset ) -{ - - if ( pBufferSize == 0 ) return false; - - unsigned long dwSizeIn = *pBufferSize; - *pBufferSize = 0; - XMP_Int64 posXMP = 0ULL, posPAD = 0ULL; - unsigned long dwLen = 0, dwExtendedTag = 0; - if ( ! FindXMPFrame ( inFileRef, posXMP, posPAD, dwExtendedTag, dwLen ) ) return false; - - // Found the XMP frame! Get the rest of frame into the buffer - unsigned long dwXMPBufferLen = dwLen - k_dwXMPLabelSize; - *pBufferSize = dwXMPBufferLen; - - if ( fileOffset != 0 ) *fileOffset = posXMP + k_dwXMPLabelSize; - - if ( buffer != 0 ) { - // Seek 4 bytes ahead to get the XMP data. - LFA_Seek ( inFileRef, posXMP+k_dwXMPLabelSize, SEEK_SET ); - if ( dwXMPBufferLen > dwSizeIn ) dwXMPBufferLen = dwSizeIn; - LFA_Read ( inFileRef, buffer, dwXMPBufferLen ); // Get the XMP frame - } - - return true; - -} - -// ================================================================================================= - -bool FindFrame ( LFA_FileRef inFileRef, char* strFrame, XMP_Int64 & posFrame, unsigned long & dwLen ) -{ - // Taking into account that the first Tag is the ID3 tag - bool fReturn = false; - LFA_Seek ( inFileRef, 0ULL, SEEK_SET ); - - #if Trace_ID3_Support - fprintf ( stderr, "ID3_Support::FindFrame : Looking for %s\n", strFrame ); - #endif - - // Read the tag name - char szID[4] = {"xxx"}; - long bytesRead = LFA_Read ( inFileRef, szID, 3 ); - if ( bytesRead == 0 ) return fReturn; - - // Check for "ID3" - if ( strcmp ( szID, "ID3" ) != 0 ) return fReturn; - - // Read the version, flag and size - XMP_Uns8 v1 = 0, v2 = 0, flags = 0; - unsigned long dwTagSize = 0; - - if ( ! GetTagInfo ( inFileRef, v1, v2, flags, dwTagSize ) ) return fReturn; - if ( dwTagSize == 0 ) return fReturn; - if ( v1 > 4 ) return fReturn; // We don't support anything newer than id3v2 4.0 - - // If there's an extended header, ignore it - XMP_Int32 dwExtendedTag = SkipExtendedHeader(inFileRef, v1, flags); - dwTagSize -= dwExtendedTag; - - // Enumerate through the frames - XMP_Int64 posCur = 0ULL; - posCur = GetFilePosition ( inFileRef ); - XMP_Int64 posEnd = posCur + dwTagSize; - - while ( posCur < posEnd ) { - - if ( (posEnd - posCur) < k_dwTagHeaderSize ) break; // Not enough room for a header, must be padding. - - char szFrameID[5] = {"xxxx"}; - unsigned long dwFrameSize = 0; - XMP_Uns8 cflag1 = 0, cflag2 = 0; - - // Get the next frame - if ( ! GetFrameInfo ( inFileRef, v1, szFrameID, cflag1, cflag2, dwFrameSize ) ) break; - - // Are we in a padding frame? - if ( dwFrameSize == 0 ) break; - - // Is it the Frame we're looking for? - if ( strcmp ( szFrameID, strFrame ) == 0 ) { - posFrame = GetFilePosition ( inFileRef ); - dwLen = dwFrameSize; - fReturn = true; - break; - } else { - // Jump to the next frame - LFA_Seek ( inFileRef, dwFrameSize, SEEK_CUR ); - } - - posCur = GetFilePosition ( inFileRef ); - } - - #if Trace_ID3_Support - if ( fReturn ) { - fprintf ( stderr, " Found %s, offset %d, length %d\n", strFrame, (long)posFrame, dwLen ); - } - #endif - - return fReturn; -} - -// ================================================================================================= - -bool GetFrameData ( LFA_FileRef inFileRef, char* strFrame, char* buffer, unsigned long &dwBufferSize ) -{ - char strData[TAG_MAX_SIZE+4]; // Plus 4 for two worst case UTF-16 nul terminators. - size_t sdPos = 0; // Offset within strData to the value. - memset ( &strData[0], 0, sizeof(strData) ); - - if ( (buffer == 0) || (dwBufferSize > TAG_MAX_SIZE) ) return false; - - const unsigned long dwSizeIn = dwBufferSize; - XMP_Int64 posFrame = 0ULL; - unsigned long dwLen = 0; - XMP_Uns8 bEncoding = 0; - - // Find the frame - if ( ! FindFrame ( inFileRef, strFrame, posFrame, dwLen ) ) return false; - #if Trace_ID3_Support - fprintf ( stderr, " Getting frame data\n" ); - #endif - - if ( dwLen <= 0 ) { - - dwBufferSize = 1; - buffer[0] = 0; - - } else { - - // Get the value for a typical text frame, having an encoding byte followed by the value. - // COMM frames are special, see below. First get encoding, and the frame data into strData. - - dwBufferSize = dwLen - 1; // Don't count the encoding byte. - - // Seek to the frame - LFA_Seek ( inFileRef, posFrame, SEEK_SET ); - - // Read the Encoding - LFA_Read ( inFileRef, &bEncoding, 1 ); - if ( bEncoding > 3 ) return false; - - // Get the frame - if ( dwBufferSize > dwSizeIn ) dwBufferSize = dwSizeIn; - - if ( dwBufferSize >= TAG_MAX_SIZE ) return false; // No room for data. - LFA_Read ( inFileRef, &strData[0], dwBufferSize ); - - if ( strcmp ( strFrame, "COMM" ) == 0 ) { - - // A COMM frame has a 3 byte language tag, then an encoded and nul terminated description - // string, then the encoded value string. Set dwOffset to the offset to the value. - - unsigned long dwOffset = 3; // Skip the 3 byte language code. - - if ( (bEncoding == 0) || (bEncoding == 3) ) { - dwOffset += (unsigned long)(strlen ( &strData[3] ) + 1); // Skip the descriptor and nul. - } else { - UTF16Unit* u16Ptr = (UTF16Unit*) (&strData[3]); - for ( ; *u16Ptr != 0; ++u16Ptr ) dwOffset += 2; // Skip the descriptor. - dwOffset += 2; // Skip the nul also. - } - - if ( dwOffset >= dwBufferSize ) return false; - dwBufferSize -= dwOffset; - - sdPos = dwOffset; - - #if Trace_ID3_Support - fprintf ( stderr, " COMM frame, dwOffset %d\n", dwOffset ); - #endif - - } - - // Encoding translation - switch ( bEncoding ) { - - case 1: // UTF-16 with a BOM. (Might be missing for empty string.) - case 2: // Big endian UTF-16 with no BOM. - { - bool bigEndian = true; // Assume big endian if no BOM. - UTF16Unit* u16Ptr = (UTF16Unit*) &strData[sdPos]; - - if ( GetUns16BE ( u16Ptr ) == 0xFEFF ) { - ++u16Ptr; // Don't translate the BOM. - } else if ( GetUns16BE ( u16Ptr ) == 0xFFFE ) { - bigEndian = false; - ++u16Ptr; // Don't translate the BOM. - } - - size_t u16Len = 0; // Count the UTF-16 units, not bytes. - for ( UTF16Unit* temp = u16Ptr; *temp != 0; ++temp ) ++u16Len; - - std::string utf8Str; - FromUTF16 ( u16Ptr, u16Len, &utf8Str, bigEndian ); - if ( utf8Str.size() >= (sizeof(strData) - sdPos) ) return false; - strcpy ( &strData[sdPos], utf8Str.c_str() ); // AUDIT: Protected by the above check. - } - break; - - case 0: // ISO Latin-1 (8859-1). - { - std::string utf8Str; - char* localPtr = &strData[sdPos]; - size_t localLen = dwBufferSize; - - ReconcileUtils::Latin1ToUTF8 ( localPtr, localLen, &utf8Str ); - if ( utf8Str.size() >= (sizeof(strData) - sdPos) ) return false; - strcpy ( &strData[sdPos], utf8Str.c_str() ); // AUDIT: Protected by the above check. - } - break; - - case 3: // UTF-8 - default: - // Handled appropriately - break; - - } - - char * strTemp = &strData[sdPos]; - - if ( strcmp ( strFrame, "TCON" ) == 0 ) { - - char str[TAG_MAX_SIZE]; - str[0] = 0; - if ( strlen ( &strData[sdPos] ) >= sizeof(str) ) return false; - strcpy ( str, &strData[sdPos] ); // AUDIT: Protected by the above check. - - #if Trace_ID3_Support - fprintf ( stderr, " TCON frame, first char '%c'\n", str[0] ); - #endif - - // Genre: let's get the "string" value - if ( str[0] == '(' ) { - int iGenre = atoi(str+1); - if ( (iGenre > 0) && (iGenre < 127) ) { - strTemp = Genres[iGenre]; - } else { - strTemp = Genres[12]; - } - } else { - // Text, let's "try" to find it anyway - int i = 0; - for ( i=0; i < 127; ++i ) { - if ( stricmp ( str, Genres[i] ) == 0 ) { - strTemp = Genres[i]; // Found, let's use the one in the list - break; - } - } - if ( i == 127 ) strTemp = Genres[12]; // Not found - } - - } - - #if Trace_ID3_Support - fprintf ( stderr, " Have data, length %d, \"%s\"\n", strlen(strTemp), strTemp ); - #endif - - if ( strlen(strTemp) >= dwSizeIn ) return false; - strcpy ( buffer, strTemp ); // AUDIT: Protected by the above check. - - } - - return true; - -} - -// ================================================================================================= - -bool AddXMPTagToID3Buffer ( char * strCur, unsigned long * pdwCurOffset, unsigned long dwMaxSize, XMP_Uns8 bVersion, - char *strFrameName, const char * strXMPTagTemp, unsigned long dwXMPLengthTemp ) -{ - char strGenre[64]; - const char * strXMPTag = strXMPTagTemp; - XMP_Int32 dwCurOffset = *pdwCurOffset; - XMP_Uns8 bEncoding = 0; - long dwXMPLength = dwXMPLengthTemp; - - if ( dwXMPLength == 0 ) return false; - - if ( strcmp ( strFrameName, "TCON" ) == 0 ) { - - // Genre: we need to get the number back... - int iFound = 12; - - for ( int i=0; i < 127; ++i ) { - if ( stricmp ( strXMPTag, Genres[i] ) == 0 ) { - iFound = i; // Found - break; - } - } - - snprintf ( strGenre, sizeof(strGenre), "(%d)", iFound ); // AUDIT: Using sizeof(strGenre) is safe. - strXMPTag = strGenre; - dwXMPLength = (long)strlen(strXMPTag); - - } - - // Stick with the ID3v2.3 encoding choices, they are a proper subset of ID3v2.4. - // 0 - ISO Latin-1 - // 1 - UTF-16 with BOM - // For 3rd party reliability we always write UTF-16 as little endian. For example, Windows - // Media Player fails to honor the BOM, it assumes little endian. - - std::string tempLatin1, tempUTF8; - ReconcileUtils::UTF8ToLatin1 ( strXMPTag, dwXMPLength, &tempLatin1 ); - ReconcileUtils::Latin1ToUTF8 ( tempLatin1.data(), tempLatin1.size(), &tempUTF8 ); - if ( ((size_t)dwXMPLength != tempUTF8.size()) || (memcmp ( strXMPTag, tempUTF8.data(), dwXMPLength ) != 0) ) { - bEncoding = 1; // Will convert to UTF-16 later. - } else { - strXMPTag = tempLatin1.c_str(); // Use the Latin-1 encoding for output. - dwXMPLength = (long)tempLatin1.size(); - } - - std::string strUTF16; - if ( bEncoding == 1 ) { - ToUTF16 ( (UTF8Unit*)strXMPTag, dwXMPLength, &strUTF16, false /* little endian */ ); - dwXMPLength = (long)strUTF16.size() + 2; // ! Include the (to be inserted) BOM in the count. - } - - // Frame Structure - // Frame ID $xx xx xx xx (four characters) - // Size 4 * %0xxxxxxx <<--- IMPORTANT NOTE: This is true only in v4.0 (v3.0 uses a UInt32) - // Flags $xx xx - // Encoding $xx (Not included in the frame header) - // Special case: "COMM" which we have to include "XXX\0" in front of it (also not included in the frame header) - - unsigned long dwFrameSize = dwXMPLength + 1; // 1 == Encoding; - - bool fCOMM = (strcmp ( strFrameName, "COMM" ) == 0); - if ( fCOMM ) { - dwFrameSize += 3; // The "XXX" language part. - dwFrameSize += ((bEncoding == 0) ? 1 : 4 ); // The empty descriptor string. - } - - if ( (dwCurOffset + k_dwFrameHeaderSize + dwFrameSize) > dwMaxSize ) return false; - - unsigned long dwCalculated = CalculateSize ( bVersion, dwFrameSize ); - - // FrameID - if ( (dwMaxSize - dwCurOffset) < 4 ) return false; - memcpy ( strCur+dwCurOffset, strFrameName, 4 ); // AUDIT: Protected by the above check. - dwCurOffset += 4; - - // Frame Size - written as big endian - strCur[dwCurOffset] = (char)(dwCalculated >> 24); - ++dwCurOffset; - strCur[dwCurOffset] = (char)((dwCalculated >> 16) & 0xFF); - ++dwCurOffset; - strCur[dwCurOffset] = (char)((dwCalculated >> 8) & 0xFF); - ++dwCurOffset; - strCur[dwCurOffset] = (char)(dwCalculated & 0xFF); - ++dwCurOffset; - - // Flags - strCur[dwCurOffset] = 0; - ++dwCurOffset; - strCur[dwCurOffset] = 0; - ++dwCurOffset; - - // Encoding - strCur[dwCurOffset] = bEncoding; - ++dwCurOffset; - - // COMM extras: XXX language and empty encoded descriptor string. - if ( fCOMM ) { - if ( (dwMaxSize - dwCurOffset) < 3 ) return false; - memcpy ( strCur+dwCurOffset, "XXX", 3 ); // AUDIT: Protected by the above check. - dwCurOffset += 3; - if ( bEncoding == 0 ) { - strCur[dwCurOffset] = 0; - ++dwCurOffset; - } else { - strCur[dwCurOffset] = 0xFF; - ++dwCurOffset; - strCur[dwCurOffset] = 0xFE; - ++dwCurOffset; - strCur[dwCurOffset] = 0; - ++dwCurOffset; - strCur[dwCurOffset] = 0; - ++dwCurOffset; - } - } - - if ( bEncoding == 1 ) { - // Add the BOM "FFFE" - strCur[dwCurOffset] = 0xFF; - ++dwCurOffset; - strCur[dwCurOffset] = 0xFE; - ++dwCurOffset; - dwXMPLength -= 2; // The BOM was included above. - // Copy the Unicode data - if ( (long)(dwMaxSize - dwCurOffset) < dwXMPLength ) return false; - memcpy ( strCur+dwCurOffset, strUTF16.data(), dwXMPLength ); // AUDIT: Protected by the above check. - dwCurOffset += dwXMPLength; - } else { - // Copy the data - if ( (long)(dwMaxSize - dwCurOffset) < dwXMPLength ) return false; - memcpy ( strCur+dwCurOffset, strXMPTag, dwXMPLength ); // AUDIT: Protected by the above check. - dwCurOffset += dwXMPLength; - } - - *pdwCurOffset = dwCurOffset; - - return true; - -} - -// ================================================================================================= - -static void OffsetAudioData ( LFA_FileRef inFileRef, XMP_Int64 audioOffset, XMP_Int64 oldAudioBase ) -{ - enum { kBuffSize = 64*1024 }; - XMP_Uns8 buffer [kBuffSize]; - - const XMP_Int64 posEOF = LFA_Measure ( inFileRef ); - XMP_Int64 posCurrentCopy; // ! Must be a signed type! - - posCurrentCopy = posEOF; - while ( posCurrentCopy >= (oldAudioBase + kBuffSize) ) { - posCurrentCopy -= kBuffSize; // *** Xcode 2.3 seemed to generate bad code using a for loop. - LFA_Seek ( inFileRef, posCurrentCopy, SEEK_SET ); - LFA_Read ( inFileRef, buffer, kBuffSize ); - LFA_Seek ( inFileRef, (posCurrentCopy + audioOffset), SEEK_SET ); - LFA_Write ( inFileRef, buffer, kBuffSize ); - } - - if ( posCurrentCopy != oldAudioBase ) { - XMP_Uns32 remainder = (XMP_Uns32) (posCurrentCopy - oldAudioBase); - XMP_Assert ( remainder < kBuffSize ); - LFA_Seek ( inFileRef, oldAudioBase, SEEK_SET ); - LFA_Read ( inFileRef, buffer, remainder ); - LFA_Seek ( inFileRef, (oldAudioBase + audioOffset), SEEK_SET ); - LFA_Write ( inFileRef, buffer, remainder ); - } - -} - -// ================================================================================================= - -bool SetMetaData ( LFA_FileRef inFileRef, char* strXMPPacket, unsigned long dwXMPPacketSize, - char* strLegacyFrames, unsigned long dwFullLegacySize, bool fRecon ) -{ - // The ID3 section layout: - // ID3 header, 10 bytes - // Unrecognized ID3 frames - // Legacy ID3 metadata frames (artist, album, genre, etc.) - // XMP frame, content is "XMP\0" plus the packet - // padding - - // ID3 Buffer vars - const unsigned long kiMaxBuffer = 100*1000; - char szID3Buffer [kiMaxBuffer]; // Must be enough for the ID3 header, unknown ID3 frames, and legacy ID3 metadata. - unsigned long id3BufferLen = 0; // The amount of stuff currently in the buffer. - - unsigned long dwOldID3ContentSize = 0; // The size of the existing ID3 content (not counting the header). - unsigned long dwNewID3ContentSize = 0; // The size of the updated ID3 content (not counting the header). - - unsigned long newPadSize = 0; - - XMP_Uns8 bMajorVersion = 3; - - bool fFoundID3 = FindID3Tag ( inFileRef, dwOldID3ContentSize, bMajorVersion ); - if ( (bMajorVersion > 4) || (bMajorVersion < 3) ) return false; // Not supported - - // Now that we know the version of the ID3 tag, let's format the size of the XMP frame. - - #define k_XMPPrefixSize (k_dwFrameHeaderSize + k_dwXMPLabelSize) - char szXMPPrefix [k_XMPPrefixSize] = { 'P', 'R', 'I', 'V', 0, 0, 0, 0, 0, 0, 'X', 'M', 'P', 0 }; - unsigned long dwXMPContentSize = k_dwXMPLabelSize + dwXMPPacketSize; - unsigned long dwFullXMPFrameSize = k_dwFrameHeaderSize + dwXMPContentSize; - - unsigned long dwFormattedTemp = CalculateSize ( bMajorVersion, dwXMPContentSize ); - - szXMPPrefix[4] = (char)(dwFormattedTemp >> 24); - szXMPPrefix[5] = (char)((dwFormattedTemp >> 16) & 0xFF); - szXMPPrefix[6] = (char)((dwFormattedTemp >> 8) & 0xFF); - szXMPPrefix[7] = (char)(dwFormattedTemp & 0xFF); - - // Set up the ID3 buffer with the ID3 header and any existing unrecognized ID3 frames. - - if ( ! fFoundID3 ) { - - // Case 1 - No id3v2 tag: Create the tag with the XMP frame. - // Create the tag - // ID3v2/file identifier "ID3" - // ID3v2 version $03 00 - // ID3v2 flags %abcd0000 - // ID3v2 size 4 * %0xxxxxxx - - XMP_Assert ( dwOldID3ContentSize == 0 ); - - char szID3Header [k_dwTagHeaderSize] = { 'I', 'D', '3', 3, 0, 0, 0, 0, 0, 0 }; - - // Copy the ID3 header - if ( sizeof(szID3Buffer) < k_dwTagHeaderSize ) return false; - memcpy ( szID3Buffer, szID3Header, k_dwTagHeaderSize ); // AUDIT: Protected by the above check. - id3BufferLen = k_dwTagHeaderSize; - - newPadSize = 100; - dwNewID3ContentSize = dwFullLegacySize + dwFullXMPFrameSize + newPadSize; - - } else { - - // Case 2 - id3v2 tag is present - // 1. Copy all the unknown tags - // 2. Make the rest padding (to be used right there). - - if ( (k_dwFrameHeaderSize + dwOldID3ContentSize) > kiMaxBuffer ) { - // The ID3Buffer is not big enough to fit the id3v2 tag... let's bail... - return false; - } - - LoadTagHeaderAndUnknownFrames ( inFileRef, szID3Buffer, sizeof(szID3Buffer), fRecon, id3BufferLen ); - - unsigned long spareLen = (k_dwFrameHeaderSize + dwOldID3ContentSize) - id3BufferLen; - - if ( spareLen >= (dwFullLegacySize + dwFullXMPFrameSize) ) { - - // The exising ID3 header can hold the update. - dwNewID3ContentSize = dwOldID3ContentSize; - newPadSize = spareLen - (dwFullLegacySize + dwFullXMPFrameSize); - - } else { - - // The existing ID3 header is too small, it will have to grow. - newPadSize = 100; - dwNewID3ContentSize = (id3BufferLen - k_dwTagHeaderSize) + - dwFullLegacySize + dwFullXMPFrameSize + newPadSize; - - } - - } - - // Move the audio data if the ID3 frame is new or has to grow. - - XMP_Assert ( dwNewID3ContentSize >= dwOldID3ContentSize ); - - if ( dwNewID3ContentSize > dwOldID3ContentSize ) { - unsigned long audioOffset = dwNewID3ContentSize - dwOldID3ContentSize; - unsigned long oldAudioBase = k_dwTagHeaderSize + dwOldID3ContentSize; - if ( ! fFoundID3 ) { - // We're injecting an entire ID3 section. - audioOffset = k_dwTagHeaderSize + dwNewID3ContentSize; - oldAudioBase = 0; - } - OffsetAudioData ( inFileRef, audioOffset, oldAudioBase ); - } - - // Set the new size for the ID3 content. This always uses the 4x7 format. - - dwFormattedTemp = CalculateSize ( 4, dwNewID3ContentSize ); - szID3Buffer[6] = (char)(dwFormattedTemp >> 24); - szID3Buffer[7] = (char)((dwFormattedTemp >> 16) & 0xFF); - szID3Buffer[8] = (char)((dwFormattedTemp >> 8) & 0xFF); - szID3Buffer[9] = (char)(dwFormattedTemp & 0xFF); - - // Write the partial ID3 buffer (ID3 header plus unknown tags) - LFA_Seek ( inFileRef, 0, SEEK_SET ); - LFA_Write ( inFileRef, szID3Buffer, id3BufferLen ); - - // Append the new legacy metadata frames - if ( dwFullLegacySize > 0 ) { - LFA_Write ( inFileRef, strLegacyFrames, dwFullLegacySize ); - } - - // Append the XMP frame prefix - LFA_Write ( inFileRef, szXMPPrefix, k_XMPPrefixSize ); - - // Append the XMP packet - LFA_Write ( inFileRef, strXMPPacket, dwXMPPacketSize ); - - // Append the padding. - if ( newPadSize > 0 ) { - std::string szPad; - szPad.reserve ( newPadSize ); - szPad.assign ( newPadSize, '\0' ); - LFA_Write ( inFileRef, const_cast<char *>(szPad.data()), newPadSize ); - } - - LFA_Flush ( inFileRef ); - - return true; - -} - -// ================================================================================================= - -bool LoadTagHeaderAndUnknownFrames ( LFA_FileRef inFileRef, char * strBuffer, size_t strBufferLen, bool fRecon, unsigned long & posPad ) -{ - - LFA_Seek ( inFileRef, 3ULL, SEEK_SET ); // Point after the "ID3" - - // Get the tag info - unsigned long dwOffset = 0; - XMP_Uns8 v1 = 0, v2 = 0, flags = 0; - unsigned long dwTagSize = 0; - GetTagInfo ( inFileRef, v1, v2, flags, dwTagSize ); - - unsigned long dwExtendedTag = SkipExtendedHeader ( inFileRef, v1, flags ); - - LFA_Seek ( inFileRef, 0ULL, SEEK_SET ); - XMP_Assert ( strBufferLen >= k_dwTagHeaderSize ); - LFA_Read ( inFileRef, strBuffer, k_dwTagHeaderSize ); - dwOffset += k_dwTagHeaderSize; - - // Completely ignore the Extended Header - if ( ((flags & flagExt) == flagExt) && (dwExtendedTag > 0) ) { - strBuffer[5] = strBuffer[5] & 0xBF; // If the flag has been set, let's reset it - LFA_Seek ( inFileRef, dwExtendedTag, SEEK_CUR ); // And let's seek up to after the extended header - } - - // Enumerate through the frames - XMP_Int64 posCur = 0ULL; - posCur = GetFilePosition ( inFileRef ); - XMP_Int64 posEnd = posCur + dwTagSize; - - while ( posCur < posEnd ) { - - XMP_Assert ( k_dwTagHeaderSize == 10 ); - if ( (posEnd - posCur) < k_dwTagHeaderSize ) break; // Not enough room for a header, must be padding. - - char szFrameID[5] = {"xxxx"}; - unsigned long dwFrameSize = 0; - XMP_Uns8 cflag1 = 0, cflag2 = 0; - - // Get the next frame - if ( ! GetFrameInfo ( inFileRef, v1, szFrameID, cflag1, cflag2, dwFrameSize ) ) break; - - // Are we in a padding frame? - if ( dwFrameSize == 0 ) break; // We just hit a padding frame - - bool fIgnore = false; - bool knownID = (strcmp ( szFrameID, "TIT2" ) == 0) || - (strcmp ( szFrameID, "TYER" ) == 0) || - (strcmp ( szFrameID, "TDRV" ) == 0) || - (strcmp ( szFrameID, "TPE1" ) == 0) || - (strcmp ( szFrameID, "TALB" ) == 0) || - (strcmp ( szFrameID, "TCON" ) == 0) || - (strcmp ( szFrameID, "COMM" ) == 0) || - (strcmp ( szFrameID, "TRCK" ) == 0); - - // If a known frame, just ignore - // Note: If recon is turned off, let's consider all known frames as unknown - if ( knownID && fRecon ) { - - fIgnore = true; - - } else if ( strcmp ( szFrameID, "PRIV" ) == 0 ) { - - // Read the "PRIV" frame - // <Header for "PRIV"> - // Short content descrip. <text string according to encoding> $00 (00) - // The actual data <full text string according to encoding> - - // Get the PRIV descriptor - char szXMPTag[4] = {"xxx"}; - if ( LFA_Read ( inFileRef, &szXMPTag, k_dwXMPLabelSize ) != 0 ) { - // Is it a XMP "PRIV" - if ( (szXMPTag[3] == 0) && (strcmp ( szXMPTag, "XMP" ) == 0) ) fIgnore = true; - LFA_Seek ( inFileRef, -(long)k_dwXMPLabelSize, SEEK_CUR ); - } - - } - - if ( fIgnore ) { - LFA_Seek ( inFileRef, dwFrameSize, SEEK_CUR ); - } else { - // Unknown frame, let's copy it - LFA_Seek ( inFileRef, -(long)k_dwFrameHeaderSize, SEEK_CUR ); - if ( (dwOffset > strBufferLen) || ((dwFrameSize + k_dwFrameHeaderSize) > (strBufferLen - dwOffset)) ) { - XMP_Throw ( "Avoiding I/O buffer overflow", kXMPErr_InternalFailure ); - } - LFA_Read ( inFileRef, (strBuffer + dwOffset), (dwFrameSize + k_dwFrameHeaderSize) ); - dwOffset += dwFrameSize+k_dwFrameHeaderSize; - } - - posCur = GetFilePosition ( inFileRef ); - - } - - posPad = dwOffset; - - return true; - -} - -// ================================================================================================= - -bool FindID3Tag ( LFA_FileRef inFileRef, unsigned long & dwLen, XMP_Uns8 & bMajorVer ) -{ - // id3v2 tag: - // ID3v2/file identifier "ID3" - // ID3v2 version $04 00 - // ID3v2 flags %abcd0000 - // ID3v2 size 4 * %0xxxxxxx - - // Taking into account that the first Tag is the ID3 tag - LFA_Seek ( inFileRef, 0ULL, SEEK_SET ); - - // Read the tag name - char szID[4] = {"xxx"}; - long bytesRead = LFA_Read ( inFileRef, szID, 3 ); - if ( bytesRead == 0 ) return false; - - // Check for "ID3" - if ( strcmp ( szID, "ID3" ) != 0 ) return false; - - // Read the version, flag and size - XMP_Uns8 v2 = 0, flags = 0; - if ( ! GetTagInfo ( inFileRef, bMajorVer, v2, flags, dwLen ) ) return false; - - return true; - -} - -// ================================================================================================= - -bool GetTagInfo ( LFA_FileRef inFileRef, XMP_Uns8 & v1, XMP_Uns8 & v2, XMP_Uns8 & flags, unsigned long & dwTagSize ) -{ - - if ((LFA_Read(inFileRef, &v1, 1)) == 0) return false; - if ((LFA_Read(inFileRef, &v2, 1)) == 0) return false; - if ((LFA_Read(inFileRef, &flags, 1)) == 0) return false; - if (!ReadSize(inFileRef, 4, dwTagSize)) return false; // Tag size is always using the size reading method. - - return true; - -} - -// ================================================================================================= - -static bool FindXMPFrame ( LFA_FileRef inFileRef, XMP_Int64 & posXMP, XMP_Int64 & posPAD, unsigned long & dwExtendedTag, unsigned long & dwLen ) -{ - // Taking into account that the first Tag is the ID3 tag - bool fReturn = false; - dwExtendedTag = 0; - posPAD = 0; - - LFA_Seek ( inFileRef, 0ULL, SEEK_SET ); - - // Read the tag name - char szID[4] = {"xxx"}; - long bytesRead = LFA_Read ( inFileRef, szID, 3 ); - if ( bytesRead == 0 ) return fReturn; - - // Check for "ID3" - if ( strcmp ( szID, "ID3") != 0 ) return fReturn; - - // Read the version, flag and size - XMP_Uns8 v1 = 0, v2 = 0, flags = 0; - unsigned long dwTagSize = 0; - if ( ! GetTagInfo ( inFileRef, v1, v2, flags, dwTagSize ) ) return fReturn; - if ( dwTagSize == 0 ) return fReturn; - if ( v1 > 4 ) return fReturn; // We don't support anything newer than id3v2 4.0 - - // If there's an extended header, ignore it - dwExtendedTag = SkipExtendedHeader(inFileRef, v1, flags); - dwTagSize -= dwExtendedTag; - - // Enumerate through the frames - XMP_Int64 posCur = 0ULL; - posCur = GetFilePosition ( inFileRef ); - XMP_Int64 posEnd = posCur + dwTagSize; - - while ( posCur < posEnd ) { - - if ( (posEnd - posCur) < k_dwTagHeaderSize ) break; // Not enough room for a header, must be padding. - - char szFrameID[5] = {"xxxx"}; - unsigned long dwFrameSize = 0; - XMP_Uns8 cflag1 = 0, cflag2 = 0; - - // Get the next frame - if ( ! GetFrameInfo ( inFileRef, v1, szFrameID, cflag1, cflag2, dwFrameSize ) ) { - // Set the file pointer to the XMP or the start - LFA_Seek ( inFileRef, fReturn ? posXMP : 0ULL, SEEK_SET ); - break; - } - - // Are we in a padding frame? - if ( dwFrameSize == 0 ) { - - // We just hit a padding frame - LFA_Seek ( inFileRef, -(long)k_dwFrameHeaderSize, SEEK_CUR ); - posPAD = GetFilePosition ( inFileRef ); - - // Set the file pointer to the XMP or the start - LFA_Seek ( inFileRef, fReturn ? posXMP : 0ULL, SEEK_SET ); - break; - - } - - // Is it a "PRIV"? - if ( strcmp(szFrameID, "PRIV") != 0 ) { - - // Jump to the next frame - LFA_Seek ( inFileRef, dwFrameSize, SEEK_CUR ); - - } else { - - // Read the "PRIV" frame - // <Header for "PRIV"> - // Short content descrip. <text string according to encoding> $00 (00) - // The actual data <full text string according to encoding> - - unsigned long dwBytesRead = 0; - - // Get the PRIV descriptor - char szXMPTag[4] = {"xxx"}; - if (LFA_Read(inFileRef, &szXMPTag, k_dwXMPLabelSize) == 0) return fReturn; - dwBytesRead += k_dwXMPLabelSize; - - // Is it a XMP "PRIV" - if ( (szXMPTag[3] == 0) && (strcmp ( szXMPTag, "XMP" ) == 0) ) { - dwLen = dwFrameSize; - LFA_Seek ( inFileRef, -(long)k_dwXMPLabelSize, SEEK_CUR ); - posXMP = GetFilePosition ( inFileRef ); - fReturn = true; - dwBytesRead -= k_dwXMPLabelSize; - } - - // Didn't find it, let skip the rest of the frame and continue - LFA_Seek ( inFileRef, dwFrameSize - dwBytesRead, SEEK_CUR ); - - } - - posCur = GetFilePosition ( inFileRef ); - - } - - return fReturn; - -} - -// ================================================================================================= - -// Returns the size of the extended header -static unsigned long SkipExtendedHeader ( LFA_FileRef inFileRef, XMP_Uns8 bVersion, XMP_Uns8 flags ) -{ - if ( flags & flagExt ) { - - unsigned long dwExtSize = 0; // <-- This will include the size (full extended header size) - - if ( ReadSize ( inFileRef, bVersion, dwExtSize ) ) { - if ( bVersion < 4 ) dwExtSize += 4; // v3 doesn't include the size, while v4 does. - LFA_Seek ( inFileRef, (size_t)(dwExtSize - 4), SEEK_CUR ); - } - - return dwExtSize; - - } - - return 0; - -} - -// ================================================================================================= - -static bool GetFrameInfo ( LFA_FileRef inFileRef, XMP_Uns8 bVersion, char * strFrameID, XMP_Uns8 & cflag1, XMP_Uns8 & cflag2, unsigned long & dwSize) -{ - // Frame ID $xx xx xx xx (four characters) - // Size 4 * %0xxxxxxx <<--- IMPORTANT NOTE: This is true only in v4.0 (v3.0 uses a XMP_Int32) - // Flags $xx xx - - if ( strFrameID == 0 ) return false; - - if ( LFA_Read ( inFileRef, strFrameID, 4 ) == 0 ) return false; - if ( ! ReadSize ( inFileRef, bVersion, dwSize ) ) return false; - if ( LFA_Read ( inFileRef, &cflag1, 1 ) == 0 ) return false; - if ( LFA_Read ( inFileRef, &cflag2, 1 ) == 0 ) return false; - - return true; - -} - -// ================================================================================================= - -static bool ReadSize ( LFA_FileRef inFileRef, XMP_Uns8 bVersion, unsigned long & dwSize ) -{ - char s4 = 0, s3 = 0, s2 = 0, s1 = 0; - - if ( LFA_Read ( inFileRef, &s4, 1 ) == 0 ) return false; - if ( LFA_Read ( inFileRef, &s3, 1 ) == 0 ) return false; - if ( LFA_Read ( inFileRef, &s2, 1 ) == 0 ) return false; - if ( LFA_Read ( inFileRef, &s1, 1 ) == 0 ) return false; - - if ( bVersion > 3 ) { - dwSize = ((s4 & 0x7f) << 21) | ((s3 & 0x7f) << 14) | ((s2 & 0x7f) << 7) | (s1 & 0x7f); - } else { - dwSize = ((s4 << 24) | (s3 << 16) | (s2 << 8) | s1); - } - - return true; - -} - -// ================================================================================================= - -static unsigned long CalculateSize ( XMP_Uns8 bVersion, unsigned long dwSizeIn ) -{ - unsigned long dwReturn; - - if ( bVersion <= 3 ) { - dwReturn = dwSizeIn; - } else { - dwReturn = dwSizeIn & 0x7f; // Expand to 7 bits per byte. - dwSizeIn = dwSizeIn >> 7; - dwReturn |= ((dwSizeIn & 0x7f) << 8); - dwSizeIn = dwSizeIn >> 7; - dwReturn |= ((dwSizeIn & 0x7f) << 16); - dwSizeIn = dwSizeIn >> 7; - dwReturn |= ((dwSizeIn & 0x7f) << 24); - } - - return dwReturn; - -} - -} // namespace ID3_Support - -// ================================================================================================= - -#endif // XMP_UNIXBuild diff --git a/source/XMPFiles/FormatSupport/ID3_Support.hpp b/source/XMPFiles/FormatSupport/ID3_Support.hpp index 975ccf4..e243d24 100644 --- a/source/XMPFiles/FormatSupport/ID3_Support.hpp +++ b/source/XMPFiles/FormatSupport/ID3_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -11,31 +11,774 @@ // ================================================================================================= #include "XMP_Environment.h" // ! This must be the first include. -#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed. - -#include <vector> - #include "XMP_Const.h" #include "XMPFiles_Impl.hpp" +#include "UnicodeConversions.hpp" +#include "Reconcile_Impl.hpp" +#include <vector> + +#if XMP_WinBuild + #define stricmp _stricmp +#else + static int stricmp ( const char * left, const char * right ) // Case insensitive ASCII compare. + { + char chL = *left; // ! Allow for 0 passes in the loop (one string is empty). + char chR = *right; // ! Return -1 for stricmp ( "a", "Z" ). -#define TAG_MAX_SIZE 5024 + for ( ; (*left != 0) && (*right != 0); ++left, ++right ) { + chL = *left; + chR = *right; + if ( chL == chR ) continue; + if ( ('A' <= chL) && (chL <= 'Z') ) chL |= 0x20; + if ( ('A' <= chR) && (chR <= 'Z') ) chR |= 0x20; + if ( chL != chR ) break; + } + + if ( chL == chR ) return 0; + if ( chL < chR ) return -1; + return 1; + } +#endif + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) namespace ID3_Support { + // Genres + static char Genres[128][32]={ + "Blues", // 0 + "Classic Rock", // 1 + "Country", // 2 + "Dance", + "Disco", + "Funk", + "Grunge", + "Hip-Hop", + "Jazz", // 8 + "Metal", + "New Age", // 10 + "Oldies", + "Other", // 12 + "Pop", + "R&B", + "Rap", + "Reggae", // 16 + "Rock", // 17 + "Techno", + "Industrial", + "Alternative", + "Ska", + "Death Metal", + "Pranks", + "Soundtrack", // 24 + "Euro-Techno", + "Ambient", + "Trip-Hop", + "Vocal", + "Jazz+Funk", + "Fusion", + "Trance", + "Classical", // 32 + "Instrumental", + "Acid", + "House", + "Game", + "Sound Clip", + "Gospel", + "Noise", + "AlternRock", + "Bass", + "Soul", //42 + "Punk", + "Space", + "Meditative", + "Instrumental Pop", + "Instrumental Rock", + "Ethnic", + "Gothic", + "Darkwave", + "Techno-Industrial", + "Electronic", + "Pop-Folk", + "Eurodance", + "Dream", + "Southern Rock", + "Comedy", + "Cult", + "Gangsta", + "Top 40", + "Christian Rap", + "Pop/Funk", + "Jungle", + "Native American", + "Cabaret", + "New Wave", // 66 + "Psychadelic", + "Rave", + "Showtunes", + "Trailer", + "Lo-Fi", + "Tribal", + "Acid Punk", + "Acid Jazz", + "Polka", + "Retro", + "Musical", + "Rock & Roll", + "Hard Rock", + "Folk", // 80 + "Folk-Rock", + "National Folk", + "Swing", + "Fast Fusion", + "Bebob", + "Latin", + "Revival", + "Celtic", + "Bluegrass", // 89 + "Avantgarde", + "Gothic Rock", + "Progressive Rock", + "Psychedelic Rock", + "Symphonic Rock", + "Slow Rock", + "Big Band", + "Chorus", + "Easy Listening", + "Acoustic", + "Humour", // 100 + "Speech", + "Chanson", + "Opera", + "Chamber Music", + "Sonata", + "Symphony", + "Booty Bass", + "Primus", + "Porn Groove", + "Satire", + "Slow Jam", + "Club", + "Tango", + "Samba", + "Folklore", + "Ballad", + "Power Ballad", + "Rhythmic Soul", + "Freestyle", + "Duet", + "Punk Rock", + "Drum Solo", + "A capella", + "Euro-House", + "Dance Hall", + "Unknown" // 126 + }; - bool GetMetaData ( LFA_FileRef inFileRef, char* buffer, unsigned long* pBufferSize, XMP_Int64* fileOffset ); - bool SetMetaData ( LFA_FileRef inFileRef, char * buffer, unsigned long bufferSize, - char * strReconciliatedFrames, unsigned long dwReconciliatedFramesSize, bool fRecon ); + ////////////////////////////////////////////////////////// + // ID3 specific helper routines + inline XMP_Int32 synchToInt32(XMP_Uns32 rawDataBE) // see part 6.2 of spec + { + XMP_Validate( 0 == (rawDataBE & 0x80808080),"input not synchsafe", kXMPErr_InternalFailure ); + XMP_Int32 r = + ( rawDataBE & 0x0000007F ) + + ( rawDataBE >> 1 & 0x00003F80 ) + + ( rawDataBE >> 2 & 0x001FC000 ) + + ( rawDataBE >> 3 & 0x0FE00000 ); + return r; + } - bool GetTagInfo ( LFA_FileRef inFileRef, XMP_Uns8 & v1, XMP_Uns8 & v2, XMP_Uns8 & flags, unsigned long & dwTagSize ); - bool FindID3Tag ( LFA_FileRef inFileRef, unsigned long & dwLen, XMP_Uns8 & bMajorVer ); + inline XMP_Uns32 int32ToSynch(XMP_Int32 value) + { + XMP_Validate( 0 <= 0x0FFFFFFF, "value too big", kXMPErr_InternalFailure ); + XMP_Uns32 r = + ( (value & 0x0000007F) << 0 ) + + ( (value & 0x00003F80) << 1 ) + + ( (value & 0x001FC000) << 2 ) + + ( (value & 0x0FE00000) << 3 ); + return r; + } - bool AddXMPTagToID3Buffer ( char * strCur, unsigned long * pdwCurOffset, unsigned long dwMaxSize, - XMP_Uns8 bVersion, char * strFrameName, const char * strXMPTag, unsigned long dwXMPLength ); + ////////////////////////////////////////////////////////// + // data structures + class ID3Header + { + public: + const static XMP_Uns16 o_id = 0; + const static XMP_Uns16 o_version_major = 3; + const static XMP_Uns16 o_version_minor = 4; + const static XMP_Uns16 o_flags = 5; + const static XMP_Uns16 o_size = 6; - bool GetFrameData ( LFA_FileRef inFileRef, char * strFrame, char * buffer, unsigned long & dwBufferSize ); + const static int FIXED_SIZE = 10; + char fields[FIXED_SIZE]; -} // namespace ID3_Support + bool read(LFA_FileRef file) + { + LFA_Read( file, fields, FIXED_SIZE, true ); + + if ( !CheckBytes( &fields[ID3Header::o_id], "ID3", 3 ) ) + { + // chuck in default contents: + const static char defaultHeader[FIXED_SIZE] = { 'I', 'D', '3', 3, 0, 0, 0, 0, 0, 0 }; + memcpy( this->fields, defaultHeader, FIXED_SIZE); // NBA: implicitly protected by FIXED_SIZE design. + + return false; // no header found (o.k.) thus stick with new, default header constructed above + } + + XMP_Uns8 major = fields[o_version_major]; + XMP_Uns8 minor = fields[o_version_minor]; + XMP_Validate( major==3 || major==4, "invalid ID3 major version", kXMPErr_BadFileFormat ); + XMP_Validate( minor != 0xFF, "invalid ID3 minor version", kXMPErr_BadFileFormat ); + + return true; + } + + void write(LFA_FileRef file, XMP_Int64 tagSize) + { + XMP_Assert( tagSize < 0x10000000 ); // 256 MB limit due to synching. + XMP_Assert( tagSize > 0x0 ); // 256 MB limit due to synching. + PutUns32BE( ( int32ToSynch ( (XMP_Uns32)tagSize - 10 )), &this->fields[ID3Header::o_size] ); + + LFA_Write( file, fields, FIXED_SIZE ); + } + + ~ID3Header() + { + //nothing + }; + }; // ID3Header + + + class ID3v2Frame + { + public: + // fixed + const static XMP_Uns16 o_id = 0; + const static XMP_Uns16 o_size = 4; // size after unsync, excludes frame header. + const static XMP_Uns16 o_flags = 8; + + const static int FIXED_SIZE = 10; + char fields[FIXED_SIZE]; + + XMP_Uns32 id; + XMP_Uns16 flags; + + bool active; //default: true. flag is lowered, if another frame with replaces this one as "last meaningful frame of its kind" + bool changed; //default: false. flag is raised, if setString() is used + + // variable-size frame content + char* content; + XMP_Int32 contentSize; // size of variable content, right as its stored in o_size (total size - 10) + + /** + * a mere empty hull for reading in frames. + */ + ID3v2Frame():id(0),content(0),contentSize(0),flags(0),active(true),changed(false) + { + memset(fields, 0, FIXED_SIZE); + } + + /** + * frame constructor + */ + ID3v2Frame( XMP_Uns32 id ):id(0),content(0),contentSize(0),flags(0),active(true),changed(false) + { + memset(fields, 0, FIXED_SIZE); + + this->id = id; + PutUns32BE( id, &fields[o_id]); + this->flags = 0x0000; + PutUns16BE( flags, &fields[o_flags]); + } + + /** + * copy constructor: + */ + ID3v2Frame( const ID3v2Frame& orig) + { + XMP_Throw("not implemented",kXMPErr_InternalFailure); + } + + /** + * sets a new Content (aka property) + */ + void setFrameValue( const std::string& rawvalue, bool needDescriptor = false , bool utf16le = false, bool isXMPPRIVFrame = false, bool needEncodingByte = true ) + { + // the thing to insert + std::string value; + value.erase(); + + if ( isXMPPRIVFrame ) + { + // this flag must be used exclusive: + XMP_Assert( ! needDescriptor ); + XMP_Assert( ! utf16le ); + + value.append( "XMP\0", 4 ); + value.append( rawvalue ); + value.append( "\0", 1 ); // final zero byte + } + else // not an XMP Priv frame + { + if (needEncodingByte) // don't add encoding byte for example for WCOP + { + if ( utf16le ) + value.append( "\x1", 1 ); // 'unicode encoding' (must be UTF-16, be or le) + else + value.append( "\x0", 1 ); + } + + if ( needDescriptor ) // language descriptor + value.append( "eng", 3 ); // language, empty content description + if ( utf16le ) + { + if ( needDescriptor ) + value.append( "\xFF\xFE\0\0", 4 ); // BOM, 16-bit zero (empty descriptor) + + value.append( "\xFF\xFE", 2 ); + std::string utf16str; + ToUTF16( (XMP_Uns8*) rawvalue.c_str(), rawvalue.size(), &utf16str, false ); + value.append( utf16str ); + value.append( "\0\0", 2 ); + } + else // write as plain Latin-1 + { + std::string convertedValue; // just precaution (mostly used for plain ascii like numbers, etc) + convertedValue.erase(); + ReconcileUtils::UTF8ToLatin1( rawvalue.c_str(), rawvalue.size(), &convertedValue ); + + if ( needDescriptor ) + value.append( "\0", 1 ); // BOM, 16-bit zero (empty descriptor) + value.append( convertedValue ); + value.append( "\0", 1 ); // final zero byte + } + } // of "not an XMP Priv frame" + + this->changed = true; + this->release(); + + XMP_StringPtr valueCStr = value.c_str(); + this->contentSize = (XMP_Int32) value.size(); + XMP_Validate( this->contentSize < 0xA00000, "XMP Property exceeds 20MB in size", kXMPErr_InternalFailure ); + content = new char[ this->contentSize ]; + memcpy( content , valueCStr , this->contentSize ); + } + + void write(LFA_FileRef file, XMP_Uns8 majorVersion) + { + // write back size field: + if ( majorVersion < 4 ) + PutUns32BE( contentSize, &fields[o_size] ); + else + PutUns32BE( int32ToSynch( contentSize ), &fields[o_size] ); + + // write out fixed fields + LFA_Write( file, this->fields, FIXED_SIZE); + // write out contents + LFA_Write( file, this->content, contentSize ); + } + + /** + * majorVersion must be 3 or 4 + * @returns total size of frame + */ + XMP_Int64 read(LFA_FileRef file, XMP_Uns8 majorVersion ) + { + XMP_Assert( majorVersion == 3 || majorVersion == 4 ); + this->release(); // ensures/allows reuse of 'curFrame' + + XMP_Int64 start = LFA_Tell( file ); + LFA_Read( file, fields, FIXED_SIZE, true ); + + id = GetUns32BE( &fields[o_id] ); + + //only padding to come? + if (id == 0x00000000) + { + LFA_Seek( file, -10, SEEK_CUR ); //rewind 10 byte + return 0; // zero signals, not a valid frame + } + //verify id to be valid (4x A-Z and 0-9) + XMP_Validate( + (( fields[0]>0x40 && fields[0]<0x5B) || ( fields[0]>0x2F && fields[0]<0x3A)) && //A-Z,0-9 + (( fields[1]>0x40 && fields[1]<0x5B) || ( fields[1]>0x2F && fields[1]<0x3A)) && + (( fields[2]>0x40 && fields[2]<0x5B) || ( fields[2]>0x2F && fields[2]<0x3A)) && + (( fields[3]>0x40 && fields[3]<0x5B) || ( fields[3]>0x2F && fields[3]<0x3A)) + ,"invalid Frame ID", kXMPErr_BadFileFormat); + + flags = GetUns16BE( &fields[o_flags] ); + XMP_Validate( 0==(flags & 0xEE ),"invalid lower bits in frame flags",kXMPErr_BadFileFormat ); + + //*** flag handling, spec :429ff aka line 431ff (i.e. Frame should be discarded) + // compression and all of that..., unsynchronisation + contentSize = ( majorVersion < 4) ? + GetUns32BE( &fields[o_size] ) + : synchToInt32(GetUns32BE( &fields[o_size] )); + + XMP_Validate( contentSize >= 0, "negative frame size", kXMPErr_BadFileFormat ); + // >20MB in a single frame?, assume broken file + XMP_Validate( contentSize < 0xA00000, "single frame exceeds 20MB", kXMPErr_BadFileFormat ); -#endif // XMP_UNIXBuild + content = new char[ contentSize ]; + + LFA_Read( file, content, contentSize, true ); + XMP_Assert( (LFA_Tell( file ) - start) == FIXED_SIZE + contentSize ); //trivial + return LFA_Tell( file ) - start; + } + + /** + * two types of COMM frames should be preserved but otherwise ignored + * * a 6-field long header just having + * encoding(1 byte),lang(3 bytes) and 0x00 31 (no descriptor, "1" as content") + * perhaps only used to indicate client language + * * COMM frames whose description begins with engiTun, these are iTunes flags + * + * returns true: job done as expted + * false: do not use this frame, but preserve (i.e. iTunes marker COMM frame) + */ + bool advancePastCOMMDescriptor(XMP_Int32& pos) + { + if ( (contentSize-pos) <= 3 ) + return false; // silent error, no room left behing language tag + + if ( !CheckBytes( &content[pos], "eng", 3 ) ) + return false; // not an error, but leave all non-eng tags alone... + + pos += 3; // skip lang tag + if ( pos >= contentSize ) + return false; // silent error + + // skip past descriptor: + while ( pos < contentSize ) + if ( content[pos++] == 0x00 ) + break; + // skip yet-another double zero if applicable + if ( (pos<contentSize) && (content[pos] == 0x00) ) + pos++; + + // check for "1" language indicator comment + if (pos == 5) + if ( contentSize == 6) + if ( GetUns16BE(&content[4]) == 0x0031 ) + return false; + + // check for iTunes tag-comment + if ( pos > 4 ) //there's a content descriptor ( 1 + 3 + zeroTerminator) + { + std::string descriptor = std::string(&content[4],pos-1); + if ( 0 == descriptor.substr(0,4).compare( "iTun" )) // begins with engiTun ? + return false; // leave alone, then + } + + return true; //o.k., descriptor skipped, time for the real thing. + } + + /* + * returns the frame content as a proper UTF8 string + * * w/o the initial encoding byte + * * dealing with BOM's + * + * @returns: by value: character string with the value + * as return value: false if frame is "not of intereset" despite a generally + * "interesting" frame ID, these are + * * iTunes-'marker' COMM frame + */ + bool getFrameValue( XMP_Uns8 majorVersion, XMP_Uns32 frameID, std::string* utf8string ) + { + XMP_Assert( contentSize >= 0 ); + XMP_Assert( contentSize < 0xA00000 ); // more than 20 MB per Propety would be very odd... + XMP_Assert( content != 0 ); + + if ( contentSize == 0) + { + utf8string->erase(); + return true; // ...it is "of interest", even if empty contents. + } + + XMP_Int32 pos = 0; // going through input string + XMP_Uns8 encByte = 0; + // WCOP does not have an encoding byte + // => for all others: use [0] as EncByte, advance pos + if ( frameID != 0x57434F50 ) + { + encByte = content[0]; + pos++; + } + + // mode specific forks (so far only COMM) + bool commMode = ( + ( frameID == 0x434F4D4D ) || // COMM + ( frameID == 0x55534C54 ) // USLT + ); + + switch (encByte) + { + case 0: //ISO-8859-1, 0-terminated + { + if (commMode) + if (! advancePastCOMMDescriptor( pos )) // do and check result + return false; // not a frame of interest! + + char* localPtr = &content[pos]; + size_t localLen = contentSize - pos; + ReconcileUtils::Latin1ToUTF8 ( localPtr, localLen, utf8string ); + } + break; + case 1: // Unicode, v2.4: UTF-16 (undetermined Endianess), with BOM, terminated 0x00 00 + case 2: // UTF-16BE without BOM, terminated 0x00 00 + { + std::string tmp(this->content, this->contentSize); + + if (commMode) + if (! advancePastCOMMDescriptor( pos )) // do and check result + return false; // not a frame of interest! + + bool bigEndian = true; // assume for now (if no BOM follows) + if ( GetUns16BE(&content[pos]) == 0xFEFF ) + { + pos += 2; + bigEndian = true; + } + else if ( GetUns16BE(&content[pos]) == 0xFFFE ) + { + pos += 2; + bigEndian = false; + } + + FromUTF16( (UTF16Unit*)&content[pos], (contentSize - pos) / 2, utf8string, bigEndian ); + } + break; + case 3: // UTF-8 unicode, terminated \0 + // swallow any BOM, just in case + if ( (GetUns32BE(&content[pos]) & 0xFFFFFF00 ) == 0xEFBBBF00 ) + pos += 3; + + if (commMode) + if (! advancePastCOMMDescriptor( pos )) // do and check result + return false; // not a frame of interest! + + utf8string->assign( &content[pos], contentSize - pos ); + break; + default: + XMP_Throw ( "unknown text encoding", kXMPErr_BadFileFormat); //COULDDO assume latin-1 or utf-8 as best-effort + break; + } + return true; + } + + ~ID3v2Frame() + { + this->release(); + } + + void release() + { + if ( content ) + delete content; + content = 0; + contentSize = 0; + } + }; // ID3Header + + + + class ID3v1Tag + { + public: + // fixed + const static XMP_Uns16 o_tag = 0; // must be "TAG" + const static XMP_Uns16 o_title = 3; + const static XMP_Uns16 o_artist = 33; + const static XMP_Uns16 o_album = 63; + const static XMP_Uns16 o_year = 93; + const static XMP_Uns16 o_comment = 97; + const static XMP_Uns16 o_genre = 127; // last by: index, or 255 + + // optional (ID3v1.1) track number) + const static XMP_Uns16 o_zero = 125; //must be zero, for trackNo to be valid + const static XMP_Uns16 o_trackNo = 126; //trackNo + + const static int FIXED_SIZE = 128; + + /** + * @returns returns true, if ID3v1 (or v1.1) exists, otherwise false. + // sets XMP properties en route + */ + bool read( LFA_FileRef file, SXMPMeta* meta ) + { + if ( LFA_Measure( file ) <= 128 ) // ensure sufficient room + return false; + LFA_Seek( file, -128, SEEK_END); + + XMP_Uns32 tagID = LFA_ReadInt32_BE( file ); + tagID = tagID & 0xFFFFFF00; // wipe 4th byte + if ( tagID != 0x54414700 ) + return false; // must be "TAG" + LFA_Seek( file, -1, SEEK_CUR ); //rewind 1 + + ///////////////////////////////////////////////////////// + XMP_Uns8 buffer[31]; // nothing is bigger here, than 30 bytes (offsets [0]-[29]) + buffer[30]=0; // wipe last byte + std::string utf8string; + + // title ////////////////////////////////////////////////////// + LFA_Read( file, buffer, 30, true ); + std::string title( (char*) buffer ); //security: guaranteed to 0-terminate after 30 bytes + if ( ! title.empty() ) + { + ReconcileUtils::Latin1ToUTF8 ( title.c_str(), title.size(), &utf8string ); + meta->SetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , utf8string.c_str() ); + } + // artist ////////////////////////////////////////////////////// + LFA_Read( file, buffer, 30, true ); + std::string artist( (char*) buffer ); + if ( ! artist.empty() ) + { + ReconcileUtils::Latin1ToUTF8 ( artist.c_str(), artist.size(), &utf8string ); + meta->SetProperty( kXMP_NS_DM, "artist", utf8string.c_str() ); + } + // album ////////////////////////////////////////////////////// + LFA_Read( file, buffer, 30, true ); + std::string album( (char*) buffer ); + if ( ! album.empty() ) + { + ReconcileUtils::Latin1ToUTF8 ( album.c_str(), album.size(), &utf8string ); + meta->SetProperty( kXMP_NS_DM, "album", utf8string.c_str() ); + } + // year ////////////////////////////////////////////////////// + LFA_Read( file, buffer, 4, true ); + buffer[4]=0; // ensure 0-term + std::string year( (char*) buffer ); + if ( ! year.empty() ) + { // should be moot for a year, but let's be safe: + ReconcileUtils::Latin1ToUTF8 ( year.c_str(), year.size(), &utf8string ); + meta->SetProperty( kXMP_NS_XMP, "CreateDate", utf8string.c_str() ); + } + // comment ////////////////////////////////////////////////////// + LFA_Read( file, buffer, 30, true ); + std::string comment( (char*) buffer ); + if ( ! comment.empty() ) + { + ReconcileUtils::Latin1ToUTF8 ( comment.c_str(), comment.size(), &utf8string ); + meta->SetProperty( kXMP_NS_DM, "logComment", utf8string.c_str() ); + } + // trackNo (ID3v1.1) ///////////////////////////////////////////// + if ( buffer[28] == 0 ) + { + XMP_Uns8 trackNo = buffer[29]; + if ( trackNo > 0 ) // 0 := unset + { + std::string trackStr; + SXMPUtils::ConvertFromInt( trackNo, 0, &trackStr ); + meta->SetProperty( kXMP_NS_DM, "trackNumber", trackStr.c_str() ); + } + } + // genre ////////////////////////////////////////////////////// + XMP_Uns8 genreNo = LFA_ReadUns8( file ); + if ( (genreNo > 0) && (genreNo < 127) ) // 0 := unset, 127 := 'unknown' + { + meta->SetProperty( kXMP_NS_DM, "genre", Genres[genreNo] ); + } + + return true; // ID3Tag found + } + + void write( LFA_FileRef file, SXMPMeta* meta ) + { + // move in position (extension if applicable happens by caller) + std::string zeros( 128, '\0' ); + std::string utf8, latin1; + + LFA_Seek( file, -128, SEEK_END); + LFA_Write( file, zeros.data(), 128 ); + + // TAG ///////////////////////////////////////////// + LFA_Seek( file, -128, SEEK_END); + LFA_WriteUns8( file, 'T' ); + LFA_WriteUns8( file, 'A' ); + LFA_WriteUns8( file, 'G' ); + + // title ////////////////////////////////////////////////////// + if ( meta->GetLocalizedText( kXMP_NS_DC, "title", "", "x-default", 0, &utf8, kXMP_NoOptions )) + { + LFA_Seek( file, -128 + 3, SEEK_END); + ReconcileUtils::UTF8ToLatin1( utf8.c_str(), utf8.size(), &latin1 ); + LFA_Write( file, latin1.c_str(), MIN( 30, (XMP_Int32)latin1.size() ) ); + } + // artist ////////////////////////////////////////////////////// + if ( meta->GetProperty( kXMP_NS_DM, "artist", &utf8, kXMP_NoOptions )) + { + LFA_Seek( file, -128 + 33, SEEK_END); + ReconcileUtils::UTF8ToLatin1( utf8.c_str(), utf8.size(), &latin1 ); + LFA_Write( file, latin1.c_str(), MIN( 30, (XMP_Int32)latin1.size() ) ); + } + // album ////////////////////////////////////////////////////// + if ( meta->GetProperty( kXMP_NS_DM, "album", &utf8, kXMP_NoOptions )) + { + LFA_Seek( file, -128 + 63, SEEK_END); + ReconcileUtils::UTF8ToLatin1( utf8.c_str(), utf8.size(), &latin1 ); + LFA_Write( file, latin1.c_str(), MIN( 30, (XMP_Int32)latin1.size() ) ); + } + // year ////////////////////////////////////////////////////// + if ( meta->GetProperty( kXMP_NS_XMP, "CreateDate", &utf8, kXMP_NoOptions )) + { + XMP_DateTime dateTime; + SXMPUtils::ConvertToDate( utf8, &dateTime ); + if ( dateTime.hasDate ) + { + SXMPUtils::ConvertFromInt ( dateTime.year, "", &latin1 ); + LFA_Seek ( file, -128 + 93, SEEK_END ); + LFA_Write ( file, latin1.c_str(), MIN ( 4, (XMP_Int32)latin1.size() ) ); + } + } + // comment (write 30 bytes first, see truncation later) //////////// + if ( meta->GetProperty( kXMP_NS_DM, "logComment", &utf8, kXMP_NoOptions )) + { + LFA_Seek ( file, -128 + 97, SEEK_END ); + ReconcileUtils::UTF8ToLatin1 ( utf8.c_str(), utf8.size(), &latin1 ); + LFA_Write ( file, latin1.c_str(), MIN ( 30, (XMP_Int32)latin1.size() ) ); + } + // genre //////////////////////////////////////////////////////////////// + if ( meta->GetProperty( kXMP_NS_DM, "genre", &utf8, kXMP_NoOptions )) + { + XMP_Uns8 genreNo = 0; + + // try to find (case insensitive) match: + int i; + const char* genreCString = utf8.c_str(); + for ( i=0; i < 127; ++i ) { + if ( (strlen( genreCString ) == strlen(Genres[i])) && //fixing buggy stricmp behaviour on PPC + (stricmp( genreCString, Genres[i] ) == 0 )) { + genreNo = i; // found + break; + } // if + } // for + + LFA_Seek( file, -128 + 127, SEEK_END); + LFA_WriteUns8( file, genreNo ); + } + + // trackNo //////////////////////////////////////////////////////////// + if ( meta->GetProperty( kXMP_NS_DM, "trackNumber", &utf8, kXMP_NoOptions )) + { + XMP_Uns8 trackNo = 0; + try + { + trackNo = (XMP_Uns8) SXMPUtils::ConvertToInt( utf8.c_str() ); + + LFA_Seek( file, -128 + 125, SEEK_END); + LFA_WriteUns8( file , 0 ); // ID3v1.1 extension + LFA_WriteUns8( file, trackNo ); + } + catch ( XMP_Error& ) + { + // forgive, just don't set this one. + } + } + } + + }; // ID3v1Tag + +} // namespace ID3_Support #endif // __ID3_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/IPTC_Support.cpp b/source/XMPFiles/FormatSupport/IPTC_Support.cpp index 48e0309..b1514dd 100644 --- a/source/XMPFiles/FormatSupport/IPTC_Support.cpp +++ b/source/XMPFiles/FormatSupport/IPTC_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -17,6 +17,11 @@ /// // ================================================================================================= +enum { kUTF8_IncomingMode = 0, kUTF8_LosslessMode = 1, kUTF8_AlwaysMode = 2 }; +#ifndef kUTF8_Mode + #define kUTF8_Mode kUTF8_AlwaysMode +#endif + const DataSetCharacteristics kKnownDataSets[] = { { kIPTC_ObjectType, kIPTC_UnmappedText, 67, "", "" }, // Not mapped to XMP. { kIPTC_IntellectualGenre, kIPTC_MapSpecial, 68, kXMP_NS_IPTCCore, "IntellectualGenre" }, // Only the name part is in the XMP. @@ -40,14 +45,14 @@ const DataSetCharacteristics kKnownDataSets[] = { kIPTC_RefService, kIPTC_UnmappedText, 10, "", "" }, // Not mapped to XMP. ! Interleave 2:45, 2:47, 2:50! { kIPTC_RefDate, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP. ! Interleave 2:45, 2:47, 2:50! { kIPTC_RefNumber, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP. ! Interleave 2:45, 2:47, 2:50! - { kIPTC_DateCreated, kIPTC_MapSpecial, 8, kXMP_NS_Photoshop, "DateCreated" }, // Combined with 2:60, TimeCreated. - { kIPTC_TimeCreated, kIPTC_MapSpecial, 11, "", "" }, // Combined with 2:55, DateCreated. - { kIPTC_DigitalCreateDate, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP. - { kIPTC_DigitalCreateTime, kIPTC_UnmappedText, 11, "", "" }, // Not mapped to XMP. + { kIPTC_DateCreated, kIPTC_MapSpecial, 8, kXMP_NS_Photoshop, "DateCreated" }, // ! Reformatted date. Combined with 2:60, TimeCreated. + { kIPTC_TimeCreated, kIPTC_UnmappedText, 11, "", "" }, // ! Combined with 2:55, DateCreated. + { kIPTC_DigitalCreateDate, kIPTC_Map3Way, 8, "", "" }, // ! 3 way Exif-IPTC-XMP date/time set. Combined with 2:63, DigitalCreateTime. + { kIPTC_DigitalCreateTime, kIPTC_UnmappedText, 11, "", "" }, // ! Combined with 2:62, DigitalCreateDate. { kIPTC_OriginProgram, kIPTC_UnmappedText, 32, "", "" }, // Not mapped to XMP. { kIPTC_ProgramVersion, kIPTC_UnmappedText, 10, "", "" }, // Not mapped to XMP. { kIPTC_ObjectCycle, kIPTC_UnmappedText, 1, "", "" }, // Not mapped to XMP. - { kIPTC_Creator, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "Author" }, // ! Aliased to dc:creator[1]. + { kIPTC_Creator, kIPTC_Map3Way, 32, "", "" }, // ! In the 3 way Exif-IPTC-XMP set. { kIPTC_CreatorJobtitle, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "AuthorsPosition" }, { kIPTC_City, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "City" }, { kIPTC_Location, kIPTC_MapSimple, 32, kXMP_NS_IPTCCore, "Location" }, @@ -58,9 +63,9 @@ const DataSetCharacteristics kKnownDataSets[] = { kIPTC_Headline, kIPTC_MapSimple, 256, kXMP_NS_Photoshop, "Headline" }, { kIPTC_Provider, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "Credit" }, { kIPTC_Source, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "Source" }, - { kIPTC_CopyrightNotice, kIPTC_MapLangAlt, 128, kXMP_NS_DC, "rights" }, + { kIPTC_CopyrightNotice, kIPTC_Map3Way, 128, "", "" }, // ! In the 3 way Exif-IPTC-XMP set. { kIPTC_Contact, kIPTC_UnmappedText, 128, "", "" }, // Not mapped to XMP. - { kIPTC_Description, kIPTC_MapLangAlt, 2000, kXMP_NS_DC, "description" }, + { kIPTC_Description, kIPTC_Map3Way, 2000, "", "" }, // ! In the 3 way Exif-IPTC-XMP set. { kIPTC_DescriptionWriter, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "CaptionWriter" }, { kIPTC_RasterizedCaption, kIPTC_UnmappedBin, 7360, "", "" }, // Not mapped to XMP. ! Binary data! { kIPTC_ImageType, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP. @@ -72,8 +77,8 @@ const DataSetCharacteristics kKnownDataSets[] = { kIPTC_AudioDuration, kIPTC_UnmappedText, 6, "", "" }, // Not mapped to XMP. { kIPTC_AudioOutcue, kIPTC_UnmappedText, 64, "", "" }, // Not mapped to XMP. { kIPTC_PreviewFormat, kIPTC_UnmappedBin, 2, "", "" }, // Not mapped to XMP. ! Binary data! - { kIPTC_PreviewFormatVers, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP. ! Binary data! - { kIPTC_PreviewData, kIPTC_UnmappedText, 256000, "", "" }, // Not mapped to XMP. ! Binary data! + { kIPTC_PreviewFormatVers, kIPTC_UnmappedBin, 2, "", "" }, // Not mapped to XMP. ! Binary data! + { kIPTC_PreviewData, kIPTC_UnmappedBin, 256000, "", "" }, // Not mapped to XMP. ! Binary data! { 255, kIPTC_MapSpecial, 0, 0, 0 } }; // ! Must be last as a sentinel. // A combination of the IPTC "Subject Reference System Guidelines" and IIMv4.1 Appendix G. @@ -142,7 +147,7 @@ static const DataSetCharacteristics* FindKnownDataSet ( XMP_Uns8 id ) // IPTC_Manager::ParseMemoryDataSets // ================================= // -// Parse the legacy IIM block, keeping information about all 2:* DataSets and size of other records. +// Parse the legacy IIM block. Offsets and lengths are kept for any 1:90 DataSet and all of Record 2. void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) { @@ -188,7 +193,8 @@ void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, boo this->utf8Encoding = false; - bool foundRec2 = false; + bool found190 = false; + bool found2xx = false; for ( ; iptcPtr <= iptcLimit; iptcPtr += dsLen ) { @@ -217,25 +223,28 @@ void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, boo if ( recNum == 0 ) continue; // Should not be a record 0. Throw instead? if ( recNum == 1 ) { - if ( (dsNum == 90) && (dsLen >= 3) ) { - if ( memcmp ( iptcPtr, "\x1B\x25\x47", 3 ) == 0 ) this->utf8Encoding = true; + if ( dsNum == 90 ) { + found190 = true; + this->offset190 = (XMP_Uns32) (dsPtr - this->iptcContent); + this->length190 = 5 + dsLen; + if ( (dsLen == 3) && (memcmp ( iptcPtr, "\x1B\x25\x47", 3 ) == 0) ) this->utf8Encoding = true; } continue; // Ignore all other record 1 DataSets. } if ( recNum == 2 ) { - if ( ! foundRec2 ) { - foundRec2 = true; - this->rec2Offset = (XMP_Uns32) (dsPtr - this->iptcContent); - this->rec2Length = this->iptcLength - this->rec2Offset; // ! In case there are no other records. + if ( ! found2xx ) { + found2xx = true; + this->offset2xx = (XMP_Uns32) (dsPtr - this->iptcContent); + this->length2xx = this->iptcLength - this->offset2xx; // ! In case there are no other records. } } else { - this->rec2Length = (XMP_Uns32) (dsPtr - (this->iptcContent + this->rec2Offset)); - break; // The records are in ascending order, done. + this->length2xx = (XMP_Uns32) (dsPtr - (this->iptcContent + this->offset2xx)); + break; // The records are in ascending order, done with 2:xx DataSets. } XMP_Assert ( recNum == 2 ); - if ( dsNum == 0 ) continue; // Ignore 2:00 when reading. + if ( dsNum == 0 ) continue; // Ignore 2:00 when reading, it is created explicitly when writing. DataSetInfo dsInfo ( dsNum, dsLen, iptcPtr ); DataSetMap::iterator dsPos = this->dataSets.find ( dsNum ); @@ -246,7 +255,7 @@ void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, boo if ( (knownDS == 0) || (knownDS->mapForm == kIPTC_MapArray) ) { repeatable = true; // Allow repeats for unknown DataSets. - } else if ( dsNum == kIPTC_SubjectCode ) { + } else if ( (dsNum == kIPTC_Creator) || (dsNum == kIPTC_SubjectCode) ) { repeatable = true; } @@ -276,7 +285,7 @@ size_t IPTC_Manager::GetDataSet ( XMP_Uns8 id, DataSetInfo* info, size_t which / if ( which >= dsCount ) return 0; // Valid range for which is 0 .. count-1. if ( info != 0 ) { - for ( size_t i = 0; i < which; ++i ) ++dsPos; // ??? dsPos += which; + for ( size_t i = 0; i < which; ++i ) ++dsPos; // Can't do "dsPos += which", no iter+int operator. *info = dsPos->second; } @@ -290,6 +299,8 @@ size_t IPTC_Manager::GetDataSet ( XMP_Uns8 id, DataSetInfo* info, size_t which / size_t IPTC_Manager::GetDataSet_UTF8 ( XMP_Uns8 id, std::string * utf8Str, size_t which /* = 0 */ ) const { + if ( utf8Str != 0 ) utf8Str->erase(); + DataSetInfo dsInfo; size_t dsCount = GetDataSet ( id, &dsInfo, which ); if ( dsCount == 0 ) return 0; @@ -297,14 +308,10 @@ size_t IPTC_Manager::GetDataSet_UTF8 ( XMP_Uns8 id, std::string * utf8Str, size_ if ( utf8Str != 0 ) { if ( this->utf8Encoding ) { utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen ); - } else { - #if ! XMP_UNIXBuild - ReconcileUtils::LocalToUTF8 ( dsInfo.dataPtr, dsInfo.dataLen, utf8Str ); - #else - // ! Hack until legacy-as-local issues are resolved for generic UNIX. - if ( ! ReconcileUtils::IsUTF8 ( dsInfo.dataPtr, dsInfo.dataLen ) ) return 0; - utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen ); - #endif + } else if ( ! ignoreLocalText ) { + ReconcileUtils::LocalToUTF8 ( dsInfo.dataPtr, dsInfo.dataLen, utf8Str ); + } else if ( ReconcileUtils::IsASCII ( dsInfo.dataPtr, dsInfo.dataLen ) ) { + utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen ); } } @@ -339,8 +346,6 @@ void IPTC_Manager::DisposeLooseValue ( DataSetInfo & dsInfo ) // ================================================================================================= // ================================================================================================= -#if ! XMP_UNIXBuild // ! Disable IPTC output for generic UNIX until the legacy-as-local issues are resolved. - // ================================================================================================= // IPTC_Writer::~IPTC_Writer // ========================= @@ -367,39 +372,57 @@ void IPTC_Writer::SetDataSet_UTF8 ( XMP_Uns8 id, const void* utf8Ptr, XMP_Uns32 // Decide which character encoding to use and get a temporary pointer to the value. - XMP_Uns8 * tempPtr; - XMP_Uns32 dataLen; - - std::string localStr, rtStr; - - if ( this->utf8Encoding ) { + XMP_Uns8 * tempPtr; + XMP_Uns32 dataLen; + std::string localStr; - // We're already using UTF-8. + if ( kUTF8_Mode == kUTF8_AlwaysMode ) { + + // Always use UTF-8. + if ( ! this->utf8Encoding ) this->ConvertToUTF8(); tempPtr = (XMP_Uns8*) utf8Ptr; dataLen = utf8Len; - } else { + } else if ( kUTF8_Mode == kUTF8_IncomingMode ) { -// *** Disable the round trip loss checking for now. We only use UTF-8 if the input had it. - - ReconcileUtils::UTF8ToLocal ( utf8Ptr, utf8Len, &localStr ); -// ReconcileUtils::LocalToUTF8 ( localStr.data(), localStr.size(), &rtStr ); - -// if ( (rtStr.size() == utf8Len) && (memcmp ( rtStr.data(), utf8Ptr, utf8Len ) == 0) ) { - - // It round-tripped without loss, keep local encoding. + // Only use UTF-8 if that was what the parsed block used. + if ( this->utf8Encoding ) { + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } else if ( ! ignoreLocalText ) { + ReconcileUtils::UTF8ToLocal ( utf8Ptr, utf8Len, &localStr ); tempPtr = (XMP_Uns8*) localStr.data(); - dataLen = (XMP_Uns32)localStr.size(); - -// } else { - -// // Had round-trip loss, change to UTF-8 for all text DataSets. -// this->ConvertToUTF8(); -// XMP_Assert ( this->utf8Encoding ); -// tempPtr = (XMP_Uns8*) utf8Ptr; -// dataLen = utf8Len; - -// } + dataLen = (XMP_Uns32) localStr.size(); + } else { + if ( ! ReconcileUtils::IsASCII ( utf8Ptr, utf8Len ) ) return; + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } + + } else if ( kUTF8_Mode == kUTF8_LosslessMode ) { + + // Convert to UTF-8 if needed to prevent round trip loss. + if ( this->utf8Encoding ) { + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } else if ( ! ignoreLocalText ) { + std::string rtStr; + ReconcileUtils::UTF8ToLocal ( utf8Ptr, utf8Len, &localStr ); + ReconcileUtils::LocalToUTF8 ( localStr.data(), localStr.size(), &rtStr ); + if ( (rtStr.size() == utf8Len) && (memcmp ( rtStr.data(), utf8Ptr, utf8Len ) == 0) ) { + tempPtr = (XMP_Uns8*) localStr.data(); // No loss, keep local encoding. + dataLen = (XMP_Uns32) localStr.size(); + } else { + this->ConvertToUTF8(); // Had loss, change everything to UTF-8. + XMP_Assert ( this->utf8Encoding ); + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } + } else { + if ( ! ReconcileUtils::IsASCII ( utf8Ptr, utf8Len ) ) return; + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } } @@ -422,7 +445,7 @@ void IPTC_Writer::SetDataSet_UTF8 ( XMP_Uns8 id, const void* utf8Ptr, XMP_Uns32 if ( knownDS->mapForm == kIPTC_MapArray ) { repeatable = true; - } else if ( id == kIPTC_SubjectCode ) { + } else if ( (id == kIPTC_Creator) || (id == kIPTC_SubjectCode) ) { repeatable = true; } @@ -500,35 +523,43 @@ void IPTC_Writer::DeleteDataSet ( XMP_Uns8 id, long which /* = -1 */ ) // IPTC_Writer::UpdateMemoryDataSets // ================================= // -// Reconstruct the entire IIM block. Start with DataSet 1:0 and 1:90 if UTF-8 encoding is used, -// then 2:0, then 2:xx DataSets that have values. This does not include any alignment padding, that -// is an artifact of some specific wrappers such as Photoshop image resources. +// Reconstruct the entire IIM block. This does not include any final alignment padding, that is an +// artifact of some specific wrappers such as Photoshop image resources. -XMP_Uns32 IPTC_Writer::UpdateMemoryDataSets ( void** dataPtr ) +void IPTC_Writer::UpdateMemoryDataSets() { - if ( ! this->changed ) { - if ( dataPtr != 0 ) *dataPtr = this->iptcContent; - return this->iptcLength; - } + if ( ! this->changed ) return; DataSetMap::iterator dsPos; DataSetMap::iterator dsEnd = this->dataSets.end(); -// if ( this->utf8Encoding ) { *** Disable round trip loss checking for now. *** -// if ( ! this->CheckRoundTripLoss() ) this->ConvertToLocal(); -// } + if ( kUTF8_Mode == kUTF8_LosslessMode ) { + if ( this->utf8Encoding ) { + if ( ! this->CheckRoundTripLoss() ) this->ConvertToLocal(); + } else { + if ( this->CheckRoundTripLoss() ) this->ConvertToUTF8(); + } + } // Compute the length of the new IIM block, including space for records other than 2. All other - // records are preserved as-is, including 1:90. If we ever start changing the encoding, we will - // have to remove any existing 1:90 and insert a new one. + // records are preserved as-is, except for 1:90. If local text is used then 1:90 is omitted, + // if UTF-8 text is used then 1:90 is written. + + XMP_Uns32 midOffset = this->offset190 + this->length190; + XMP_Uns32 midLength = this->offset2xx - midOffset; + + XMP_Uns32 suffixOffset = this->offset2xx + this->length2xx; + XMP_Uns32 suffixLength = this->iptcLength - suffixOffset; - XMP_Uns32 newLength = (5+2); // For 2:0. - newLength += (this->iptcLength - rec2Length); // For records other than 2. + XMP_Uns32 newLength = this->offset190 + (5+2); // For things before 1:90 and 2:0. + if ( this->utf8Encoding ) newLength += (5+3); // For 1:90, if written. + newLength += midLength; // For things between 1:90 and 2:xx. + newLength += suffixLength; // For records after 2. - for ( dsPos = this->dataSets.begin(); dsPos != dsEnd; ++dsPos ) { + for ( dsPos = this->dataSets.begin(); dsPos != dsEnd; ++dsPos ) { // Accumulate the 2:xx sizes. XMP_Uns32 dsLen = dsPos->second.dataLen; newLength += (5 + dsLen); - if ( dsLen > 0x7FFF ) newLength += 4; // We always use a 4 byte extended length. + if ( dsLen > 0x7FFF ) newLength += 4; // We always use a 4 byte extended length for big values. } // Allocate the new IIM block. @@ -538,25 +569,35 @@ XMP_Uns32 IPTC_Writer::UpdateMemoryDataSets ( void** dataPtr ) XMP_Uns8* dsPtr = newContent; - XMP_Uns32 prefixLength = this->rec2Offset; - XMP_Uns32 suffixOffset = this->rec2Offset + this->rec2Length; - XMP_Uns32 suffixLength = this->iptcLength - suffixOffset; + // Write the 1:xx DataSets, including whatever is appropriate for 1:90. + + if ( this->offset190 > 0 ) { // Whatever was before an existing 1:90. + memcpy ( dsPtr, this->iptcContent, this->offset190 ); // AUDIT: Within range of allocation. + dsPtr += this->offset190; + } - if ( prefixLength > 0 ) { // Write the records before 2. - memcpy ( dsPtr, this->iptcContent, prefixLength ); // AUDIT: Within range of allocation. - dsPtr += prefixLength; + if ( this->utf8Encoding ) { // Write 1:90 only if text is UTF-8. + memcpy ( dsPtr, "\x1C\x01\x5A\x00\x03\x1B\x25\x47", (5+3) ); // AUDIT: Within range of allocation. + dsPtr += (5+3); } - if ( ! this->utf8Encoding ) { + if ( midLength > 0 ) { // Write the remainder before record 2. + memcpy ( dsPtr, (this->iptcContent + midOffset), midLength ); // AUDIT: Within range of allocation. + dsPtr += midLength; + } + + // Write the 2:xx DataSets, starting with an explicit 2:00. + + if ( this->utf8Encoding ) { + // Start with 2:00 for version 4. + memcpy ( dsPtr, "\x1C\x02\x00\x00\x02\x00\x04", (5+2) ); // AUDIT: Within range of allocation. + dsPtr += (5+2); + } else { // Start with 2:00 for version 2. // *** We should probably write version 4 all the time. This is a late CS3 change, don't want // *** to risk breaking other apps that might be strict about version checking. memcpy ( dsPtr, "\x1C\x02\x00\x00\x02\x00\x02", (5+2) ); // AUDIT: Within range of allocation. dsPtr += (5+2); - } else { - // Start with 2:00 for version 4. - memcpy ( dsPtr, "\x1C\x02\x00\x00\x02\x00\x04", (5+2) ); // AUDIT: Within range of allocation. - dsPtr += (5+2); } // Fill in the record 2 DataSets that have values. @@ -601,15 +642,8 @@ XMP_Uns32 IPTC_Writer::UpdateMemoryDataSets ( void** dataPtr ) XMP_Assert ( this->iptcLength == newLength ); this->ownedContent = (newLength > 0); // We really do own the new content, if not empty. - // Done. - - if ( dataPtr != 0 ) *dataPtr = this->iptcContent; - return this->iptcLength; - } // IPTC_Writer::UpdateMemoryDataSets -#if 0 // *** Disable the round trip loss checking for now. - // ================================================================================================= // IPTC_Writer::ConvertToUTF8 // ========================== @@ -631,7 +665,7 @@ void IPTC_Writer::ConvertToUTF8() ReconcileUtils::LocalToUTF8 ( dsInfo.dataPtr, dsInfo.dataLen, &utf8Str ); this->DisposeLooseValue ( dsInfo ); - dsInfo.dataLen = utf8Str.size(); + dsInfo.dataLen = (XMP_Uns32)utf8Str.size(); dsInfo.dataPtr = (XMP_Uns8*) malloc ( dsInfo.dataLen ); if ( dsInfo.dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); memcpy ( dsInfo.dataPtr, utf8Str.data(), dsInfo.dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above. @@ -663,7 +697,7 @@ void IPTC_Writer::ConvertToLocal() ReconcileUtils::UTF8ToLocal ( dsInfo.dataPtr, dsInfo.dataLen, &localStr ); this->DisposeLooseValue ( dsInfo ); - dsInfo.dataLen = localStr.size(); + dsInfo.dataLen = (XMP_Uns32)localStr.size(); dsInfo.dataPtr = (XMP_Uns8*) malloc ( dsInfo.dataLen ); if ( dsInfo.dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); memcpy ( dsInfo.dataPtr, localStr.data(), dsInfo.dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above. @@ -708,8 +742,4 @@ bool IPTC_Writer::CheckRoundTripLoss() } // IPTC_Writer::CheckRoundTripLoss -#endif // Round-trip loss checking - // ================================================================================================= - -#endif // ! XMP_UNIXBuild diff --git a/source/XMPFiles/FormatSupport/IPTC_Support.hpp b/source/XMPFiles/FormatSupport/IPTC_Support.hpp index 25ac9c2..470a6d4 100644 --- a/source/XMPFiles/FormatSupport/IPTC_Support.hpp +++ b/source/XMPFiles/FormatSupport/IPTC_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -114,11 +114,12 @@ enum { // List of recognized 2:* IIM DataSets. The names are from IIMv4 and IPTC kIPTC_PreviewData = 202 }; -enum { // Forms of mapping legacy IPTC to XMP. +enum { // Forms of mapping legacy IPTC to XMP. Order is significant, see PhotoDataUtils::Import2WayIPTC! kIPTC_MapSimple, // The XMP is simple, the last DataSet occurrence is kept. kIPTC_MapLangAlt, // The XMP is a LangAlt x-default item, the last DataSet occurrence is kept. kIPTC_MapArray, // The XMP is an unordered array, all DataSets are kept. kIPTC_MapSpecial, // The mapping requires DataSet specific code. + kIPTC_Map3Way, // Has a 3 way mapping between Exif, IPTC, and XMP. kIPTC_UnmappedText, // A text DataSet that is not mapped to XMP. kIPTC_UnmappedBin // A binary DataSet that is not mapped to XMP. }; @@ -201,12 +202,18 @@ public: bool UsingUTF8() const { return this->utf8Encoding; }; + // -------------------------------------------------- + // Update the DataSets to reflect the changed values. + + virtual void UpdateMemoryDataSets() = 0; + // --------------------------------------------------------------------------------------------- - // Update the DataSets to reflect the changed values. Returns the new size of the DataSets. The - // returned dataPtr must be treated as read only. It exists until the IPTC_Manager destructor - // is called. Can be used with read-only instances to get the raw data block info. + // Get the location and size of the full IPTC block. The client must call UpdateMemoryDataSets + // first if appropriate. The returned dataPtr must be treated as read only. It exists until the + // IPTC_Manager destructor is called. - virtual XMP_Uns32 UpdateMemoryDataSets ( void** dataPtr ) = 0; + XMP_Uns32 GetBlockInfo ( void** dataPtr ) const + { if ( dataPtr != 0 ) *dataPtr = this->iptcContent; return this->iptcLength; }; // --------------------------------------------------------------------------------------------- @@ -221,13 +228,13 @@ protected: DataSetMap dataSets; XMP_Uns8* iptcContent; - XMP_Uns32 iptcLength, rec2Offset, rec2Length; + XMP_Uns32 iptcLength, offset190, length190, offset2xx, length2xx; bool changed; bool ownedContent; // True if IPTC_Manager destructor needs to release the content block. bool utf8Encoding; // True if text values are encoded as UTF-8. - IPTC_Manager() : iptcContent(0), iptcLength(0), rec2Offset(0), rec2Length(0), + IPTC_Manager() : iptcContent(0), iptcLength(0), offset190(0), length190(0), offset2xx(0), length2xx(0), changed(false), ownedContent(false), utf8Encoding(false) {}; void DisposeLooseValue ( DataSetInfo & dsInfo ); @@ -254,8 +261,7 @@ public: bool IsChanged() { return false; }; - XMP_Uns32 UpdateMemoryDataSets ( void** dataPtr ) - { if ( dataPtr != 0 ) *dataPtr = iptcContent; return iptcLength; }; + void UpdateMemoryDataSets() { NotAppropriate(); }; virtual ~IPTC_Reader() {}; @@ -269,8 +275,6 @@ private: // IPTC_Writer // =========== -#if ! XMP_UNIXBuild // ! Disable IPTC output for generic UNIX until the legacy-as-local issues are resolved. - class IPTC_Writer : public IPTC_Manager { public: @@ -280,7 +284,7 @@ public: bool IsChanged() { return changed; }; - XMP_Uns32 UpdateMemoryDataSets ( void** dataPtr ); + void UpdateMemoryDataSets (); IPTC_Writer() {}; @@ -288,19 +292,13 @@ public: private: -#if 0 - void ConvertToUTF8(); void ConvertToLocal(); bool CheckRoundTripLoss(); - -#endif // *** Disable the round trip loss checking for now. }; // IPTC_Writer -#endif // ! XMP_UNIXBuild - // ================================================================================================= #endif // __IPTC_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.cpp b/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.cpp new file mode 100644 index 0000000..7d94734 --- /dev/null +++ b/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.cpp @@ -0,0 +1,149 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "ISOBaseMedia_Support.hpp" +#include "XMPFiles_Impl.hpp" + +// ================================================================================================= +/// \file ISOBaseMedia_Support.cpp +/// \brief Manager for parsing and serializing ISO Base Media files ( MPEG-4 and JPEG-2000). +/// +// ================================================================================================= + +namespace ISOMedia { + +static BoxInfo voidInfo; + +// ================================================================================================= +// GetBoxInfo - from memory +// ======================== + +const XMP_Uns8 * GetBoxInfo ( const XMP_Uns8 * boxPtr, const XMP_Uns8 * boxLimit, + BoxInfo * info, bool throwErrors /* = false */ ) +{ + XMP_Uns32 u32Size; + + if ( info == 0 ) info = &voidInfo; + info->boxType = info->headerSize = 0; + info->contentSize = 0; + + if ( boxPtr >= boxLimit ) XMP_Throw ( "Bad offset to GetBoxInfo", kXMPErr_InternalFailure ); + + if ( (boxLimit - boxPtr) < 8 ) { // Is there enough space for a standard box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO box header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxPtr); + return boxLimit; + } + + u32Size = GetUns32BE ( boxPtr ); + info->boxType = GetUns32BE ( boxPtr+4 ); + + if ( u32Size >= 8 ) { + info->headerSize = 8; // Normal explicit size case. + info->contentSize = u32Size - 8; + } else if ( u32Size == 0 ) { + info->headerSize = 8; // The box goes to EoF - treat it as "to limit". + info->contentSize = (boxLimit - boxPtr) - 8; + } else if ( u32Size != 1 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box size, 2..7", kXMPErr_BadFileFormat ); + info->headerSize = 8; // Bad total size in the range of 2..7, treat as 8. + info->contentSize = 0; + } else { + if ( (boxLimit - boxPtr) < 16 ) { // Is there enough space for an extended box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO extended header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxPtr); + return boxLimit; + } + XMP_Uns64 u64Size = GetUns64BE ( boxPtr+8 ); + if ( u64Size < 16 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO extended box size, < 16", kXMPErr_BadFileFormat ); + u64Size = 16; // Treat bad total size as 16. + } + info->headerSize = 16; + info->contentSize = u64Size - 16; + } + + XMP_Assert ( (XMP_Uns64)(boxLimit - boxPtr) >= (XMP_Uns64)info->headerSize ); + if ( info->contentSize > (XMP_Uns64)((boxLimit - boxPtr) - info->headerSize) ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box content size", kXMPErr_BadFileFormat ); + info->contentSize = ((boxLimit - boxPtr) - info->headerSize); // Trim a bad content size to the limit. + } + + return (boxPtr + info->headerSize + info->contentSize); + +} // GetBoxInfo + +// ================================================================================================= +// GetBoxInfo - from a file +// ======================== + +XMP_Uns64 GetBoxInfo ( LFA_FileRef fileRef, const XMP_Uns64 boxOffset, const XMP_Uns64 boxLimit, + BoxInfo * info, bool doSeek /* = true */, bool throwErrors /* = false */ ) +{ + XMP_Uns8 buffer [8]; + XMP_Uns32 u32Size; + + if ( info == 0 ) info = &voidInfo; + info->boxType = info->headerSize = 0; + info->contentSize = 0; + + if ( boxOffset >= boxLimit ) XMP_Throw ( "Bad offset to GetBoxInfo", kXMPErr_InternalFailure ); + + if ( (boxLimit - boxOffset) < 8 ) { // Is there enough space for a standard box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO box header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxOffset); + return boxLimit; + } + + if ( doSeek ) LFA_Seek ( fileRef, boxOffset, SEEK_SET ); + (void) LFA_Read ( fileRef, buffer, 8, kLFA_RequireAll ); + + u32Size = GetUns32BE ( &buffer[0] ); + info->boxType = GetUns32BE ( &buffer[4] ); + + if ( u32Size >= 8 ) { + info->headerSize = 8; // Normal explicit size case. + info->contentSize = u32Size - 8; + } else if ( u32Size == 0 ) { + info->headerSize = 8; // The box goes to EoF. + info->contentSize = LFA_Measure(fileRef) - (boxOffset + 8); + } else if ( u32Size != 1 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box size, 2..7", kXMPErr_BadFileFormat ); + info->headerSize = 8; // Bad total size in the range of 2..7, treat as 8. + info->contentSize = 0; + } else { + if ( (boxLimit - boxOffset) < 16 ) { // Is there enough space for an extended box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO extended header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxOffset); + return boxLimit; + } + (void) LFA_Read ( fileRef, buffer, 8, kLFA_RequireAll ); + XMP_Uns64 u64Size = GetUns64BE ( &buffer[0] ); + if ( u64Size < 16 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO extended box size, < 16", kXMPErr_BadFileFormat ); + u64Size = 16; // Treat bad total size as 16. + } + info->headerSize = 16; + info->contentSize = u64Size - 16; + } + + XMP_Assert ( (boxLimit - boxOffset) >= info->headerSize ); + if ( info->contentSize > (boxLimit - boxOffset - info->headerSize) ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box content size", kXMPErr_BadFileFormat ); + info->contentSize = (boxLimit - boxOffset - info->headerSize); // Trim a bad content size to the limit. + } + + return (boxOffset + info->headerSize + info->contentSize); + +} // GetBoxInfo + +} // namespace ISO_Media + + +// ================================================================================================= diff --git a/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.hpp b/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.hpp new file mode 100644 index 0000000..4f50df7 --- /dev/null +++ b/source/XMPFiles/FormatSupport/ISOBaseMedia_Support.hpp @@ -0,0 +1,100 @@ +#ifndef __ISOBaseMedia_Support_hpp__ +#define __ISOBaseMedia_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMP_Environment.h" // ! This must be the first include. + +#include "XMP_Const.h" +#include "LargeFileAccess.hpp" + +// ================================================================================================= +/// \file ISOBaseMedia_Support.hpp +/// \brief XMPFiles support for the ISO Base Media File Format. +/// +/// \note These classes are for use only when directly compiled and linked. They should not be +/// packaged in a DLL by themselves. They do not provide any form of C++ ABI protection. +// ================================================================================================= + +namespace ISOMedia { + + enum { + k_ftyp = 0x66747970UL, // File header Box, no version/flags. + + k_mp41 = 0x6D703431UL, // Compatible brand codes + k_mp42 = 0x6D703432UL, + k_f4v = 0x66347620UL, + k_qt = 0x71742020UL, + + k_moov = 0x6D6F6F76UL, // Container Box, no version/flags. + k_mvhd = 0x6D766864UL, // Data FullBox, has version/flags. + k_hdlr = 0x68646C72UL, + k_udta = 0x75647461UL, // Container Box, no version/flags. + k_cprt = 0x63707274UL, // Data FullBox, has version/flags. + k_uuid = 0x75756964UL, // Data Box, no version/flags. + k_free = 0x66726565UL, // Free space Box, no version/flags. + k_mdat = 0x6D646174UL, // Media data Box, no version/flags. + + k_trak = 0x7472616BUL, // Types for the QuickTime timecode track. + k_tkhd = 0x746B6864UL, + k_mdia = 0x6D646961UL, + k_mdhd = 0x6D646864UL, + k_tmcd = 0x746D6364UL, + k_mhlr = 0x6D686C72UL, + k_minf = 0x6D696E66UL, + k_stbl = 0x7374626CUL, + k_stsd = 0x73747364UL, + k_stsc = 0x73747363UL, + k_stco = 0x7374636FUL, + k_co64 = 0x636F3634UL, + + k_meta = 0x6D657461UL, // Types for the iTunes metadata boxes. + k_ilst = 0x696C7374UL, + k_mdir = 0x6D646972UL, + k_mean = 0x6D65616EUL, + k_name = 0x6E616D65UL, + k_data = 0x64617461UL, + k_hyphens = 0x2D2D2D2DUL, + + k_skip = 0x736B6970UL, // Additional classic QuickTime top level boxes. + k_wide = 0x77696465UL, + k_pnot = 0x706E6F74UL, + + k_XMP_ = 0x584D505FUL // The QuickTime variant XMP box. + }; + + static XMP_Uns32 k_xmpUUID [4] = { MakeUns32BE ( 0xBE7ACFCBUL ), + MakeUns32BE ( 0x97A942E8UL ), + MakeUns32BE ( 0x9C719994UL ), + MakeUns32BE ( 0x91E3AFACUL ) }; + + struct BoxInfo { + XMP_Uns32 boxType; // In memory as native endian! + XMP_Uns32 headerSize; // Normally 8 or 16, less than 8 if available space is too small. + XMP_Uns64 contentSize; // Always the real size, never 0 for "to EoF". + BoxInfo() : boxType(0), headerSize(0), contentSize(0) {}; + }; + + // Get basic info about a box in memory, returning a pointer to the following box. + const XMP_Uns8 * GetBoxInfo ( const XMP_Uns8 * boxPtr, const XMP_Uns8 * boxLimit, + BoxInfo * info, bool throwErrors = false ); + + // Get basic info about a box in a file, returning the offset of the following box. The I/O + // pointer is left at the start of the box's content. Returns the offset of the following box. + XMP_Uns64 GetBoxInfo ( LFA_FileRef fileRef, const XMP_Uns64 boxOffset, const XMP_Uns64 boxLimit, + BoxInfo * info, bool doSeek = true, bool throwErrors = false ); + +// XMP_Uns32 CountChildBoxes ( LFA_FileRef fileRef, const XMP_Uns64 childOffset, const XMP_Uns64 childLimit ); + +} // namespace ISO_Media + +// ================================================================================================= + +#endif // __ISOBaseMedia_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/MOOV_Support.cpp b/source/XMPFiles/FormatSupport/MOOV_Support.cpp new file mode 100644 index 0000000..1c89176 --- /dev/null +++ b/source/XMPFiles/FormatSupport/MOOV_Support.cpp @@ -0,0 +1,542 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "MOOV_Support.hpp" + +#include "ISOBaseMedia_Support.hpp" + +#include <string.h> + +// ================================================================================================= +/// \file MOOV_Support.cpp +/// \brief XMPFiles support for the 'moov' box in MPEG-4 and QuickTime files. +// ================================================================================================= + +// ================================================================================================= +// ================================================================================================= +// MOOV_Manager - The parsing and reading routines are all commmon +// ================================================================================================= +// ================================================================================================= + +#ifndef TraceParseMoovTree + #define TraceParseMoovTree 0 +#endif + +#ifndef TraceUpdateMoovTree + #define TraceUpdateMoovTree 0 +#endif + +// ================================================================================================= +// MOOV_Manager::PickContentPtr +// ============================ + +XMP_Uns8 * MOOV_Manager::PickContentPtr ( const BoxNode & node ) const +{ + if ( node.contentSize == 0 ) { + return 0; + } else if ( node.changed ) { + return (XMP_Uns8*) &node.changedContent[0]; + } else { + return (XMP_Uns8*) &this->fullSubtree[0] + node.offset + node.headerSize; + } +} // MOOV_Manager::PickContentPtr + +// ================================================================================================= +// MOOV_Manager::FillBoxInfo +// ========================= + +void MOOV_Manager::FillBoxInfo ( const BoxNode & node, BoxInfo * info ) const +{ + if ( info == 0 ) return; + + info->boxType = node.boxType; + info->childCount = (XMP_Uns32)node.children.size(); + info->contentSize = node.contentSize; + info->content = PickContentPtr ( node ); + +} // MOOV_Manager::FillBoxInfo + +// ================================================================================================= +// MOOV_Manager::GetBox +// ==================== +// +// Find a box given the type path. Pick the first child of each type. + +MOOV_Manager::BoxRef MOOV_Manager::GetBox ( const char * boxPath, BoxInfo * info ) const +{ + size_t pathLen = strlen(boxPath); + XMP_Assert ( (pathLen >= 4) && XMP_LitNMatch ( boxPath, "moov", 4 ) ); + if ( info != 0 ) memset ( info, 0, sizeof(BoxInfo) ); + + const char * pathPtr = boxPath + 5; // Skip the "moov/" portion. + const char * pathEnd = boxPath + pathLen; + + BoxRef currRef = &this->moovNode; + + while ( pathPtr < pathEnd ) { + + XMP_Assert ( (pathEnd - pathPtr) >= 4 ); + XMP_Uns32 boxType = GetUns32BE ( pathPtr ); + pathPtr += 5; // ! Don't care that the last step goes 1 too far. + + currRef = this->GetTypeChild ( currRef, boxType, 0 ); + if ( currRef == 0 ) return 0; + + } + + this->FillBoxInfo ( *((BoxNode*)currRef), info ); + return currRef; + +} // MOOV_Manager::GetBox + +// ================================================================================================= +// MOOV_Manager::GetNthChild +// ========================= + +MOOV_Manager::BoxRef MOOV_Manager::GetNthChild ( BoxRef parentRef, size_t childIndex, BoxInfo * info ) const +{ + XMP_Assert ( parentRef != 0 ); + const BoxNode & parent = *((BoxNode*)parentRef); + if ( info != 0 ) memset ( info, 0, sizeof(BoxInfo) ); + + if ( childIndex >= parent.children.size() ) return 0; + + const BoxNode & currNode = parent.children[childIndex]; + + this->FillBoxInfo ( currNode, info ); + return &currNode; + +} // MOOV_Manager::GetNthChild + +// ================================================================================================= +// MOOV_Manager::GetTypeChild +// ========================== + +MOOV_Manager::BoxRef MOOV_Manager::GetTypeChild ( BoxRef parentRef, XMP_Uns32 childType, BoxInfo * info ) const +{ + XMP_Assert ( parentRef != 0 ); + const BoxNode & parent = *((BoxNode*)parentRef); + if ( info != 0 ) memset ( info, 0, sizeof(BoxInfo) ); + if ( parent.children.empty() ) return 0; + + size_t i = 0, limit = parent.children.size(); + for ( ; i < limit; ++i ) { + const BoxNode & currNode = parent.children[i]; + if ( currNode.boxType == childType ) { + this->FillBoxInfo ( currNode, info ); + return &currNode; + } + } + + return 0; + +} // MOOV_Manager::GetTypeChild + +// ================================================================================================= +// MOOV_Manager::GetParsedOffset +// ============================= + +XMP_Uns32 MOOV_Manager::GetParsedOffset ( BoxRef ref ) const +{ + XMP_Assert ( ref != 0 ); + const BoxNode & node = *((BoxNode*)ref); + + if ( node.changed ) return 0; + return node.offset; + +} // MOOV_Manager::GetParsedOffset + +// ================================================================================================= +// MOOV_Manager::GetHeaderSize +// =========================== + +XMP_Uns32 MOOV_Manager::GetHeaderSize ( BoxRef ref ) const +{ + XMP_Assert ( ref != 0 ); + const BoxNode & node = *((BoxNode*)ref); + + if ( node.changed ) return 0; + return node.headerSize; + +} // MOOV_Manager::GetHeaderSize + +// ================================================================================================= +// MOOV_Manager::ParseMemoryTree +// ============================= +// +// Parse the fullSubtree data, building the BoxNode tree for the stuff that we care about. Tolerate +// errors like content ending too soon, make a best effoert to parse what we can. + +void MOOV_Manager::ParseMemoryTree ( XMP_Uns8 fileMode ) +{ + this->fileMode = fileMode; + + this->moovNode.offset = this->moovNode.boxType = 0; + this->moovNode.headerSize = this->moovNode.contentSize = 0; + this->moovNode.children.clear(); + this->moovNode.changedContent.clear(); + this->moovNode.changed = false; + + if ( this->fullSubtree.empty() ) return; + + ISOMedia::BoxInfo moovInfo; + const XMP_Uns8 * moovOrigin = &this->fullSubtree[0]; + const XMP_Uns8 * moovLimit = moovOrigin + this->fullSubtree.size(); + + (void) ISOMedia::GetBoxInfo ( moovOrigin, moovLimit, &moovInfo ); + XMP_Enforce ( moovInfo.boxType == ISOMedia::k_moov ); + + XMP_Uns64 fullMoovSize = moovInfo.headerSize + moovInfo.contentSize; + if ( fullMoovSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe. + XMP_Throw ( "Oversize 'moov' box", kXMPErr_EnforceFailure ); + } + + this->moovNode.boxType = ISOMedia::k_moov; + this->moovNode.headerSize = moovInfo.headerSize; + this->moovNode.contentSize = (XMP_Uns32)moovInfo.contentSize; + + bool ignoreMetaBoxes = (fileMode == kFileIsTraditionalQT); // ! Don't want, these don't follow ISO spec. + #if TraceParseMoovTree + fprintf ( stderr, "Parsing 'moov' subtree, moovNode @ 0x%X, ignoreMetaBoxes = %d\n", + &this->moovNode, ignoreMetaBoxes ); + #endif + this->ParseNestedBoxes ( &this->moovNode, "moov", ignoreMetaBoxes ); + +} // MOOV_Manager::ParseMemoryTree + +// ================================================================================================= +// MOOV_Manager::ParseNestedBoxes +// ============================== +// +// Add the current level of child boxes to the parent node, recurse as appropriate. + +void MOOV_Manager::ParseNestedBoxes ( BoxNode * parentNode, const std::string & parentPath, bool ignoreMetaBoxes ) +{ + ISOMedia::BoxInfo isoInfo; + const XMP_Uns8 * moovOrigin = &this->fullSubtree[0]; + const XMP_Uns8 * childOrigin = moovOrigin + parentNode->offset + parentNode->headerSize; + const XMP_Uns8 * childLimit = childOrigin + parentNode->contentSize; + const XMP_Uns8 * nextChild; + + parentNode->contentSize = 0; // Exclude nested box size. + if ( parentNode->boxType == ISOMedia::k_meta ) { // ! The 'meta' box is a FullBox. + parentNode->contentSize = 4; + childOrigin += 4; + } + + for ( const XMP_Uns8 * currChild = childOrigin; currChild < childLimit; currChild = nextChild ) { + + nextChild = ISOMedia::GetBoxInfo ( currChild, childLimit, &isoInfo ); + if ( (isoInfo.boxType == 0) && + (isoInfo.headerSize < 8) && + (isoInfo.contentSize == 0) ) continue; // Skip trailing padding that QT sometimes writes. + + XMP_Uns32 childOffset = (XMP_Uns32) (currChild - moovOrigin); + parentNode->children.push_back ( BoxNode ( childOffset, isoInfo.boxType, isoInfo.headerSize, (XMP_Uns32)isoInfo.contentSize ) ); + BoxNode * newChild = &parentNode->children.back(); + + #if TraceParseMoovTree + size_t depth = (parentPath.size()+1) / 5; + for ( size_t i = 0; i < depth; ++i ) fprintf ( stderr, " " ); + XMP_Uns32 be32 = MakeUns32BE ( newChild->boxType ); + XMP_Uns32 addr32 = (XMP_Uns32) this->PickContentPtr ( *newChild ); + fprintf ( stderr, " Parsed %s/%.4s, offset 0x%X, size %d, content @ 0x%X, BoxNode @ 0x%X\n", + parentPath.c_str(), &be32, newChild->offset, newChild->contentSize, addr32, newChild ); + #endif + + const char * pathSuffix = 0; // Set to non-zero for boxes of interest. + char buffer[6]; buffer[0] = 0; + + switch ( isoInfo.boxType ) { // Want these boxes regardless of parent. + case ISOMedia::k_udta : pathSuffix = "/udta"; break; + case ISOMedia::k_meta : pathSuffix = "/meta"; break; + case ISOMedia::k_ilst : pathSuffix = "/ilst"; break; + case ISOMedia::k_trak : pathSuffix = "/trak"; break; + case ISOMedia::k_mdia : pathSuffix = "/mdia"; break; + case ISOMedia::k_minf : pathSuffix = "/minf"; break; + case ISOMedia::k_stbl : pathSuffix = "/stbl"; break; + } + if ( pathSuffix != 0 ) { + this->ParseNestedBoxes ( newChild, (parentPath + pathSuffix), ignoreMetaBoxes ); + } + + } + +} // MOOV_Manager::ParseNestedBoxes + +// ================================================================================================= +// MOOV_Manager::NoteChange +// ======================== + +void MOOV_Manager::NoteChange() +{ + + this->moovNode.changed = true; + +} // MOOV_Manager::NoteChange + +// ================================================================================================= +// MOOV_Manager::SetBox +// ==================== +// +// Save the new data, set this box's changed flag, and set the top changed flag. + +void MOOV_Manager::SetBox ( BoxRef theBox, const void* dataPtr, XMP_Uns32 size ) +{ + XMP_Enforce ( size < moovBoxSizeLimit ); + BoxNode * node = (BoxNode*)theBox; + + if ( node->contentSize == size ) { + + XMP_Uns8 * oldContent = PickContentPtr ( *node ); + if ( memcmp ( oldContent, dataPtr, size ) == 0 ) return; // No change. + memcpy ( oldContent, dataPtr, size ); // Update the old content in-place + this->moovNode.changed = true; + + #if TraceUpdateMoovTree + XMP_Uns32 be32 = MakeUns32BE ( node->boxType ); + fprintf ( stderr, "Updated '%.4s', parse offset 0x%X, same size\n", &be32, node->offset ); + #endif + + } else { + + node->changedContent.assign ( size, 0 ); // Fill with 0's first to get the storage. + memcpy ( &node->changedContent[0], dataPtr, size ); + node->contentSize = size; + node->changed = true; + this->moovNode.changed = true; + + #if TraceUpdateMoovTree + XMP_Uns32 be32 = MakeUns32BE ( node->boxType ); + XMP_Uns32 addr32 = (XMP_Uns32) this->PickContentPtr ( *node ); + fprintf ( stderr, "Updated '%.4s', parse offset 0x%X, new size %d, new content @ 0x%X\n", + &be32, node->offset, node->contentSize, addr32 ); + #endif + + } + +} // MOOV_Manager::SetBox + +// ================================================================================================= +// MOOV_Manager::SetBox +// ==================== +// +// Like above, but create the path to the box if necessary. + +void MOOV_Manager::SetBox ( const char * boxPath, const void* dataPtr, XMP_Uns32 size ) +{ + XMP_Enforce ( size < moovBoxSizeLimit ); + + size_t pathLen = strlen(boxPath); + XMP_Assert ( (pathLen >= 4) && XMP_LitNMatch ( boxPath, "moov", 4 ) ); + + const char * pathPtr = boxPath + 5; // Skip the "moov/" portion. + const char * pathEnd = boxPath + pathLen; + + BoxRef parentRef = 0; + BoxRef currRef = &this->moovNode; + + while ( pathPtr < pathEnd ) { + + XMP_Assert ( (pathEnd - pathPtr) >= 4 ); + XMP_Uns32 boxType = GetUns32BE ( pathPtr ); + pathPtr += 5; // ! Don't care that the last step goes 1 too far. + + parentRef = currRef; + currRef = this->GetTypeChild ( parentRef, boxType, 0 ); + if ( currRef == 0 ) currRef = this->AddChildBox ( parentRef, boxType, 0, 0 ); + + } + + this->SetBox ( currRef, dataPtr, size ); + +} // MOOV_Manager::SetBox + +// ================================================================================================= +// MOOV_Manager::AddChildBox +// ========================= + +MOOV_Manager::BoxRef MOOV_Manager::AddChildBox ( BoxRef parentRef, XMP_Uns32 childType, const void* dataPtr, XMP_Uns32 size ) +{ + BoxNode * parent = (BoxNode*)parentRef; + XMP_Assert ( parent != 0 ); + + parent->children.push_back ( BoxNode ( 0, childType, 0, 0 ) ); + BoxNode * newNode = &parent->children.back(); + this->SetBox ( newNode, dataPtr, size ); + + return newNode; + +} // MOOV_Manager::AddChildBox + +// ================================================================================================= +// MOOV_Manager::DeleteNthChild +// ============================ + +bool MOOV_Manager::DeleteNthChild ( BoxRef parentRef, size_t childIndex ) +{ + BoxNode * parent = (BoxNode*)parentRef; + + if ( childIndex >= parent->children.size() ) return false; + + parent->children.erase ( parent->children.begin() + childIndex ); + return true; + +} // MOOV_Manager::DeleteNthChild + +// ================================================================================================= +// MOOV_Manager::DeleteTypeChild +// ============================= + +bool MOOV_Manager::DeleteTypeChild ( BoxRef parentRef, XMP_Uns32 childType ) +{ + BoxNode * parent = (BoxNode*)parentRef; + + BoxListPos child = parent->children.begin(); + BoxListPos limit = parent->children.end(); + + for ( ; child != limit; ++child ) { + if ( child->boxType == childType ) { + parent->children.erase ( child ); + this->moovNode.changed = true; + return true; + } + } + + return false; + +} // MOOV_Manager::DeleteTypeChild + +// ================================================================================================= +// MOOV_Manager::NewSubtreeSize +// ============================ +// +// Determine the new (changed) size of a subtree. Ignore 'free' and 'wide' boxes. + +XMP_Uns32 MOOV_Manager::NewSubtreeSize ( const BoxNode & node, const std::string & parentPath ) +{ + XMP_Uns32 subtreeSize = 8 + node.contentSize; // All boxes will have 8 byte headers. + + if ( (node.boxType == ISOMedia::k_free) || (node.boxType == ISOMedia::k_wide) ) { + } + + for ( size_t i = 0, limit = node.children.size(); i < limit; ++i ) { + + char suffix[6]; + suffix[0] = '/'; + PutUns32BE ( node.boxType, &suffix[1] ); + suffix[5] = 0; + std::string nodePath = parentPath + suffix; + + subtreeSize += this->NewSubtreeSize ( node.children[i], nodePath ); + XMP_Enforce ( subtreeSize < moovBoxSizeLimit ); + + } + + return subtreeSize; + +} // MOOV_Manager::NewSubtreeSize + +// ================================================================================================= +// MOOV_Manager::AppendNewSubtree +// ============================== +// +// Append this node's header, content, and children. Because the 'meta' box is a FullBox with nested +// boxes, there can be both content and children. Ignore 'free' and 'wide' boxes. + +#define IncrNewPtr(count) { newPtr += count; XMP_Enforce ( newPtr <= newEnd ); } + +#if TraceUpdateMoovTree + static XMP_Uns8 * newOrigin; +#endif + +XMP_Uns8 * MOOV_Manager::AppendNewSubtree ( const BoxNode & node, const std::string & parentPath, + XMP_Uns8 * newPtr, XMP_Uns8 * newEnd ) +{ + if ( (node.boxType == ISOMedia::k_free) || (node.boxType == ISOMedia::k_wide) ) { + } + + XMP_Assert ( (node.boxType != ISOMedia::k_meta) ? (node.children.empty() || (node.contentSize == 0)) : + (node.children.empty() || (node.contentSize == 4)) ); + + XMP_Enforce ( (XMP_Uns32)(newEnd - newPtr) >= (8 + node.contentSize) ); + + #if TraceUpdateMoovTree + XMP_Uns32 be32 = MakeUns32BE ( node.boxType ); + XMP_Uns32 newOffset = (XMP_Uns32) (newPtr - newOrigin); + XMP_Uns32 addr32 = (XMP_Uns32) this->PickContentPtr ( node ); + fprintf ( stderr, " Appending %s/%.4s @ 0x%X, size %d, content @ 0x%X\n", + parentPath.c_str(), &be32, newOffset, node.contentSize, addr32 ); + #endif + + // Leave the size as 0 for now, append the type and content. + + XMP_Uns8 * boxOrigin = newPtr; // Save origin to fill in the final size. + PutUns32BE ( node.boxType, (newPtr + 4) ); + IncrNewPtr ( 8 ); + + if ( node.contentSize != 0 ) { + const XMP_Uns8 * content = PickContentPtr( node ); + memcpy ( newPtr, content, node.contentSize ); + IncrNewPtr ( node.contentSize ); + } + + // Append the nested boxes. + + if ( ! node.children.empty() ) { + + char suffix[6]; + suffix[0] = '/'; + PutUns32BE ( node.boxType, &suffix[1] ); + suffix[5] = 0; + std::string nodePath = parentPath + suffix; + + for ( size_t i = 0, limit = node.children.size(); i < limit; ++i ) { + newPtr = this->AppendNewSubtree ( node.children[i], nodePath, newPtr, newEnd ); + } + + } + + // Fill in the final size. + + PutUns32BE ( (XMP_Uns32)(newPtr - boxOrigin), boxOrigin ); + + return newPtr; + +} // MOOV_Manager::AppendNewSubtree + +// ================================================================================================= +// MOOV_Manager::UpdateMemoryTree +// ============================== + +void MOOV_Manager::UpdateMemoryTree() +{ + if ( ! this->IsChanged() ) return; + + XMP_Uns32 newSize = this->NewSubtreeSize ( this->moovNode, "" ); + XMP_Enforce ( newSize < moovBoxSizeLimit ); + + RawDataBlock newData; + newData.assign ( newSize, 0 ); // Prefill with zeroes, can't append multiple items to a vector. + + XMP_Uns8 * newPtr = &newData[0]; + XMP_Uns8 * newEnd = newPtr + newSize; + + #if TraceUpdateMoovTree + fprintf ( stderr, "Starting MOOV_Manager::UpdateMemoryTree\n" ); + newOrigin = newPtr; + #endif + + XMP_Uns8 * trueEnd = this->AppendNewSubtree ( this->moovNode, "", newPtr, newEnd ); + XMP_Enforce ( trueEnd == newEnd ); + + this->fullSubtree.swap ( newData ); + this->ParseMemoryTree ( this->fileMode ); + +} // MOOV_Manager::UpdateMemoryTree diff --git a/source/XMPFiles/FormatSupport/MOOV_Support.hpp b/source/XMPFiles/FormatSupport/MOOV_Support.hpp new file mode 100644 index 0000000..3e19a9d --- /dev/null +++ b/source/XMPFiles/FormatSupport/MOOV_Support.hpp @@ -0,0 +1,215 @@ +#ifndef __MOOV_Support_hpp__ +#define __MOOV_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMP_Environment.h" // ! This must be the first include. + +#include <vector> + +#include "XMP_Const.h" +#include "XMPFiles_Impl.hpp" +#include "EndianUtils.hpp" + +typedef vector<XMP_Uns8> RawDataBlock; + +#define moovBoxSizeLimit 100*1024*1024 + +// ================================================================================================= +// MOOV_Manager +// ============ + +class MOOV_Manager { +public: + + // --------------------------------------------------------------------------------------------- + // Types and constants + + enum { // Values for fileMode. + kFileIsNormalISO = 0, // A "normal" MPEG-4 file, no 'qt ' compatible brand. + kFileIsModernQT = 1, // Has an 'ftyp' box and 'qt ' compatible brand. + kFileIsTraditionalQT = 2 // Old QuickTime, no 'ftyp' box. + }; + + typedef const void * BoxRef; // Valid until a sibling or higher box is added or deleted. + + struct BoxInfo { + XMP_Uns32 boxType; // In memory as native endian, compares work with ISOMedia::k_* constants. + XMP_Uns32 childCount; // ! A 'meta' box has both content (version/flags) and children! + XMP_Uns32 contentSize; // Does not include the size of nested boxes. + const XMP_Uns8 * content; // Null if contentSize is zero. + BoxInfo() : boxType(0), childCount(0), contentSize(0), content(0) {}; + }; + + // --------------------------------------------------------------------------------------------- + // GetBox - Pick a box given a '/' separated list of box types. Picks the 1st of each type. + // GetNthChild - Pick the overall n-th child of the parent, zero based. + // GetTypeChild - Pick the first child of the given type. + // GetParsedOffset - Get the box's offset in the parsed tree, 0 if changed since parsing. + // GetHeaderSize - Get the box's header size in the parsed tree, 0 if changed since parsing. + + BoxRef GetBox ( const char * boxPath, BoxInfo * info ) const; + + BoxRef GetNthChild ( BoxRef parentRef, size_t childIndex, BoxInfo * info ) const; + BoxRef GetTypeChild ( BoxRef parentRef, XMP_Uns32 childType, BoxInfo * info ) const; + + XMP_Uns32 GetParsedOffset ( BoxRef ref ) const; + XMP_Uns32 GetHeaderSize ( BoxRef ref ) const; + + // --------------------------------------------------------------------------------------------- + // NoteChange - Note overall change, value was directly replaced. + // SetBox(ref) - Replace the content with a copy of the given data. + // SetBox(path) - Like above, but creating path to the box if necessary. + // AddChildBox - Add a child of the given type, using a copy of the given data (may be null) + + void NoteChange(); + + void SetBox ( BoxRef theBox, const void* dataPtr, XMP_Uns32 size ); + void SetBox ( const char * boxPath, const void* dataPtr, XMP_Uns32 size ); + + BoxRef AddChildBox ( BoxRef parentRef, XMP_Uns32 childType, const void * dataPtr, XMP_Uns32 size ); + + // --------------------------------------------------------------------------------------------- + // DeleteNthChild - Delete the overall n-th child, return true if there was one. + // DeleteTypeChild - Delete the first child of the given type, return true if there was one. + + bool DeleteNthChild ( BoxRef parentRef, size_t childIndex ); + bool DeleteTypeChild ( BoxRef parentRef, XMP_Uns32 childType ); + + // --------------------------------------------------------------------------------------------- + + bool IsChanged() const { return this->moovNode.changed; }; + + // --------------------------------------------------------------------------------------------- + // The client is expected to fill in fullSubtree before calling ParseMemoryTree, and directly + // use fullSubtree after calling UpdateMemoryTree. + // + // IMPORTANT: We only support cases where the 'moov' subtree is significantly less than 4 GB, in + // particular with a threshold of probably 100 MB. This has 2 big impacts: we can safely use + // 32-bit offsets and sizes, and comfortably assume everything will fit in available heap space. + + RawDataBlock fullSubtree; // The entire 'moov' box, straight from the file or from UpdateMemoryTree. + + void ParseMemoryTree ( XMP_Uns8 fileMode ); + void UpdateMemoryTree(); + + // --------------------------------------------------------------------------------------------- + + #pragma pack (1) // ! These must match the file layout! + + struct Content_mvhd_0 { + XMP_Uns32 vFlags; // 0 + XMP_Uns32 creationTime; // 4 + XMP_Uns32 modificationTime; // 8 + XMP_Uns32 timescale; // 12 + XMP_Uns32 duration; // 16 + XMP_Int32 rate; // 20 + XMP_Int16 volume; // 24 + XMP_Uns16 pad_1; // 26 + XMP_Uns32 pad_2, pad_3; // 28 + XMP_Int32 matrix [9]; // 36 + XMP_Uns32 preDef [6]; // 72 + XMP_Uns32 nextTrackID; // 96 + }; // 100 + + struct Content_mvhd_1 { + XMP_Uns32 vFlags; // 0 + XMP_Uns64 creationTime; // 4 + XMP_Uns64 modificationTime; // 12 + XMP_Uns32 timescale; // 20 + XMP_Uns64 duration; // 24 + XMP_Int32 rate; // 32 + XMP_Int16 volume; // 36 + XMP_Uns16 pad_1; // 38 + XMP_Uns32 pad_2, pad_3; // 40 + XMP_Int32 matrix [9]; // 48 + XMP_Uns32 preDef [6]; // 84 + XMP_Uns32 nextTrackID; // 108 + }; // 112 + + struct Content_hdlr { // An 'hdlr' box as defined by ISO 14496-12. Maps OK to the QuickTime box. + XMP_Uns32 versionFlags; // 0 + XMP_Uns32 preDef; // 4 + XMP_Uns32 handlerType; // 8 + XMP_Uns32 reserved [3]; // 12 + // Plus optional component name string, null terminated UTF-8. + }; // 24 + + struct Content_stsd_entry { + XMP_Uns32 entrySize; // 0 + XMP_Uns32 format; // 4 + XMP_Uns8 reserved_1 [6]; // 8 + XMP_Uns16 dataRefIndex; // 14 + XMP_Uns32 reserved_2; // 16 + XMP_Uns32 flags; // 20 + XMP_Uns32 timeScale; // 24 + XMP_Uns32 frameDuration; // 28 + XMP_Uns8 frameCount; // 32 + XMP_Uns8 reserved_3; // 33 + // Plus optional trailing ISO boxes. + }; // 34 + + struct Content_stsc_entry { + XMP_Uns32 firstChunkNumber; // 0 + XMP_Uns32 samplesPerChunk; // 4 + XMP_Uns32 sampleDescrID; // 8 + }; // 12 + + // --------------------------------------------------------------------------------------------- + + MOOV_Manager() : fileMode(0) + { + XMP_Assert ( sizeof ( Content_mvhd_0 ) == 100 ); // Make sure the structs really are packed. + XMP_Assert ( sizeof ( Content_mvhd_1 ) == 112 ); + XMP_Assert ( sizeof ( Content_hdlr ) == 24 ); + XMP_Assert ( sizeof ( Content_stsd_entry ) == 34 ); + XMP_Assert ( sizeof ( Content_stsc_entry ) == 12 ); + }; + + virtual ~MOOV_Manager() {}; + +private: + + struct BoxNode; + typedef std::vector<BoxNode> BoxList; + typedef BoxList::iterator BoxListPos; + + struct BoxNode { + // ! Don't have a parent link, it will get destroyed by vector growth! + + XMP_Uns32 offset; // The offset in the fullSubtree, 0 if not in the parse. + XMP_Uns32 boxType; + XMP_Uns32 headerSize; // The actual header size in the fullSubtree, 0 if not in the parse. + XMP_Uns32 contentSize; // The current content size, does not include nested boxes. + BoxList children; + RawDataBlock changedContent; // Might be empty even if changed is true. + bool changed; // If true, the content is in changedContent, else in fullSubtree. + + BoxNode() : offset(0), boxType(0), headerSize(0), contentSize(0), changed(false) {}; + BoxNode ( XMP_Uns32 _offset, XMP_Uns32 _boxType, XMP_Uns32 _headerSize, XMP_Uns32 _contentSize ) + : offset(_offset), boxType(_boxType), headerSize(_headerSize), contentSize(_contentSize), changed(false) {}; + + }; + + XMP_Uns8 fileMode; + BoxNode moovNode; + + void ParseNestedBoxes ( BoxNode * parentNode, const std::string & parentPath, bool ignoreMetaBoxes ); + + XMP_Uns8 * PickContentPtr ( const BoxNode & node ) const; + void FillBoxInfo ( const BoxNode & node, BoxInfo * info ) const; + + XMP_Uns32 NewSubtreeSize ( const BoxNode & node, const std::string & parentPath ); + XMP_Uns8 * AppendNewSubtree ( const BoxNode & node, const std::string & parentPath, + XMP_Uns8 * newPtr, XMP_Uns8 * newEnd ); + +}; // MOOV_Manager + +#endif // __MOOV_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/MacScriptExtracts.h b/source/XMPFiles/FormatSupport/MacScriptExtracts.h new file mode 100644 index 0000000..9856183 --- /dev/null +++ b/source/XMPFiles/FormatSupport/MacScriptExtracts.h @@ -0,0 +1,244 @@ +#ifndef __MacScriptExtracts__ +#define __MacScriptExtracts__ + +// Extracts of script (smXyz) and language (langXyz) enums from Apple's old Script.h. +// These are used to support "traditional" QuickTime metadata processing. + +/* + Script codes: + These specify a Mac OS encoding that is related to a FOND ID range. + Some of the encodings have several variants (e.g. for different localized systems) + which all share the same script code. + Not all of these script codes are currently supported by Apple software. + Notes: + - Script code 0 (smRoman) is also used (instead of smGreek) for the Greek encoding + in the Greek localized system. + - Script code 28 (smEthiopic) is also used for the Inuit encoding in the Inuktitut + system. +*/ +enum { + smRoman = 0, + smJapanese = 1, + smTradChinese = 2, /* Traditional Chinese*/ + smKorean = 3, + smArabic = 4, + smHebrew = 5, + smGreek = 6, + smCyrillic = 7, + smRSymbol = 8, /* Right-left symbol*/ + smDevanagari = 9, + smGurmukhi = 10, + smGujarati = 11, + smOriya = 12, + smBengali = 13, + smTamil = 14, + smTelugu = 15, + smKannada = 16, /* Kannada/Kanarese*/ + smMalayalam = 17, + smSinhalese = 18, + smBurmese = 19, + smKhmer = 20, /* Khmer/Cambodian*/ + smThai = 21, + smLao = 22, + smGeorgian = 23, + smArmenian = 24, + smSimpChinese = 25, /* Simplified Chinese*/ + smTibetan = 26, + smMongolian = 27, + smEthiopic = 28, + smGeez = 28, /* Synonym for smEthiopic*/ + smCentralEuroRoman = 29, /* For Czech, Slovak, Polish, Hungarian, Baltic langs*/ + smVietnamese = 30, + smExtArabic = 31, /* extended Arabic*/ + smUninterp = 32 /* uninterpreted symbols, e.g. palette symbols*/ +}; + +/* Extended script code for full Unicode input*/ +enum { + smUnicodeScript = 0x7E +}; + +/* Obsolete script code names (kept for backward compatibility):*/ +enum { + smChinese = 2, /* (Use smTradChinese or smSimpChinese)*/ + smRussian = 7, /* Use smCyrillic*/ + /* smMaldivian = 25: deleted, no code for Maldivian*/ + smLaotian = 22, /* Use smLao */ + smAmharic = 28, /* Use smEthiopic or smGeez*/ + smSlavic = 29, /* Use smCentralEuroRoman*/ + smEastEurRoman = 29, /* Use smCentralEuroRoman*/ + smSindhi = 31, /* Use smExtArabic*/ + smKlingon = 32 +}; + +/* + Language codes: + These specify a language implemented using a particular Mac OS encoding. + Not all of these language codes are currently supported by Apple software. +*/ +enum { + langEnglish = 0, /* smRoman script*/ + langFrench = 1, /* smRoman script*/ + langGerman = 2, /* smRoman script*/ + langItalian = 3, /* smRoman script*/ + langDutch = 4, /* smRoman script*/ + langSwedish = 5, /* smRoman script*/ + langSpanish = 6, /* smRoman script*/ + langDanish = 7, /* smRoman script*/ + langPortuguese = 8, /* smRoman script*/ + langNorwegian = 9, /* (Bokmal) smRoman script*/ + langHebrew = 10, /* smHebrew script*/ + langJapanese = 11, /* smJapanese script*/ + langArabic = 12, /* smArabic script*/ + langFinnish = 13, /* smRoman script*/ + langGreek = 14, /* Greek script (monotonic) using smRoman script code*/ + langIcelandic = 15, /* modified smRoman/Icelandic script*/ + langMaltese = 16, /* Roman script*/ + langTurkish = 17, /* modified smRoman/Turkish script*/ + langCroatian = 18, /* modified smRoman/Croatian script*/ + langTradChinese = 19, /* Chinese (Mandarin) in traditional characters*/ + langUrdu = 20, /* smArabic script*/ + langHindi = 21, /* smDevanagari script*/ + langThai = 22, /* smThai script*/ + langKorean = 23 /* smKorean script*/ +}; + +enum { + langLithuanian = 24, /* smCentralEuroRoman script*/ + langPolish = 25, /* smCentralEuroRoman script*/ + langHungarian = 26, /* smCentralEuroRoman script*/ + langEstonian = 27, /* smCentralEuroRoman script*/ + langLatvian = 28, /* smCentralEuroRoman script*/ + langSami = 29, /* language of the Sami people of N. Scandinavia */ + langFaroese = 30, /* modified smRoman/Icelandic script */ + langFarsi = 31, /* modified smArabic/Farsi script*/ + langPersian = 31, /* Synonym for langFarsi*/ + langRussian = 32, /* smCyrillic script*/ + langSimpChinese = 33, /* Chinese (Mandarin) in simplified characters*/ + langFlemish = 34, /* smRoman script*/ + langIrishGaelic = 35, /* smRoman or modified smRoman/Celtic script (without dot above) */ + langAlbanian = 36, /* smRoman script*/ + langRomanian = 37, /* modified smRoman/Romanian script*/ + langCzech = 38, /* smCentralEuroRoman script*/ + langSlovak = 39, /* smCentralEuroRoman script*/ + langSlovenian = 40, /* modified smRoman/Croatian script*/ + langYiddish = 41, /* smHebrew script*/ + langSerbian = 42, /* smCyrillic script*/ + langMacedonian = 43, /* smCyrillic script*/ + langBulgarian = 44, /* smCyrillic script*/ + langUkrainian = 45, /* modified smCyrillic/Ukrainian script*/ + langByelorussian = 46, /* smCyrillic script*/ + langBelorussian = 46 /* Synonym for langByelorussian */ +}; + +enum { + langUzbek = 47, /* Cyrillic script*/ + langKazakh = 48, /* Cyrillic script*/ + langAzerbaijani = 49, /* Azerbaijani in Cyrillic script*/ + langAzerbaijanAr = 50, /* Azerbaijani in Arabic script*/ + langArmenian = 51, /* smArmenian script*/ + langGeorgian = 52, /* smGeorgian script*/ + langMoldavian = 53, /* smCyrillic script*/ + langKirghiz = 54, /* Cyrillic script*/ + langTajiki = 55, /* Cyrillic script*/ + langTurkmen = 56, /* Cyrillic script*/ + langMongolian = 57, /* Mongolian in smMongolian script*/ + langMongolianCyr = 58, /* Mongolian in Cyrillic script*/ + langPashto = 59, /* Arabic script*/ + langKurdish = 60, /* smArabic script*/ + langKashmiri = 61, /* Arabic script*/ + langSindhi = 62, /* Arabic script*/ + langTibetan = 63, /* smTibetan script*/ + langNepali = 64, /* smDevanagari script*/ + langSanskrit = 65, /* smDevanagari script*/ + langMarathi = 66, /* smDevanagari script*/ + langBengali = 67, /* smBengali script*/ + langAssamese = 68, /* smBengali script*/ + langGujarati = 69, /* smGujarati script*/ + langPunjabi = 70 /* smGurmukhi script*/ +}; + +enum { + langOriya = 71, /* smOriya script*/ + langMalayalam = 72, /* smMalayalam script*/ + langKannada = 73, /* smKannada script*/ + langTamil = 74, /* smTamil script*/ + langTelugu = 75, /* smTelugu script*/ + langSinhalese = 76, /* smSinhalese script*/ + langBurmese = 77, /* smBurmese script*/ + langKhmer = 78, /* smKhmer script*/ + langLao = 79, /* smLao script*/ + langVietnamese = 80, /* smVietnamese script*/ + langIndonesian = 81, /* smRoman script*/ + langTagalog = 82, /* Roman script*/ + langMalayRoman = 83, /* Malay in smRoman script*/ + langMalayArabic = 84, /* Malay in Arabic script*/ + langAmharic = 85, /* smEthiopic script*/ + langTigrinya = 86, /* smEthiopic script*/ + langOromo = 87, /* smEthiopic script*/ + langSomali = 88, /* smRoman script*/ + langSwahili = 89, /* smRoman script*/ + langKinyarwanda = 90, /* smRoman script*/ + langRuanda = 90, /* synonym for langKinyarwanda*/ + langRundi = 91, /* smRoman script*/ + langNyanja = 92, /* smRoman script*/ + langChewa = 92, /* synonym for langNyanja*/ + langMalagasy = 93, /* smRoman script*/ + langEsperanto = 94 /* Roman script*/ +}; + +enum { + langWelsh = 128, /* modified smRoman/Celtic script*/ + langBasque = 129, /* smRoman script*/ + langCatalan = 130, /* smRoman script*/ + langLatin = 131, /* smRoman script*/ + langQuechua = 132, /* smRoman script*/ + langGuarani = 133, /* smRoman script*/ + langAymara = 134, /* smRoman script*/ + langTatar = 135, /* Cyrillic script*/ + langUighur = 136, /* Arabic script*/ + langDzongkha = 137, /* (lang of Bhutan) smTibetan script*/ + langJavaneseRom = 138, /* Javanese in smRoman script*/ + langSundaneseRom = 139, /* Sundanese in smRoman script*/ + langGalician = 140, /* smRoman script*/ + langAfrikaans = 141 /* smRoman script */ +}; + +enum { + langBreton = 142, /* smRoman or modified smRoman/Celtic script */ + langInuktitut = 143, /* Inuit script using smEthiopic script code */ + langScottishGaelic = 144, /* smRoman or modified smRoman/Celtic script */ + langManxGaelic = 145, /* smRoman or modified smRoman/Celtic script */ + langIrishGaelicScript = 146, /* modified smRoman/Gaelic script (using dot above) */ + langTongan = 147, /* smRoman script */ + langGreekAncient = 148, /* Classical Greek, polytonic orthography */ + langGreenlandic = 149, /* smRoman script */ + langAzerbaijanRoman = 150, /* Azerbaijani in Roman script */ + langNynorsk = 151 /* Norwegian Nyorsk in smRoman*/ +}; + +enum { + langUnspecified = 32767 /* Special code for use in resources (such as 'itlm') */ +}; + +/* + Obsolete language code names (kept for backward compatibility): + Misspelled, ambiguous, misleading, considered pejorative, archaic, etc. +*/ +enum { + langPortugese = 8, /* Use langPortuguese*/ + langMalta = 16, /* Use langMaltese*/ + langYugoslavian = 18, /* (use langCroatian, langSerbian, etc.)*/ + langChinese = 19, /* (use langTradChinese or langSimpChinese)*/ + langLettish = 28, /* Use langLatvian */ + langLapponian = 29, /* Use langSami*/ + langLappish = 29, /* Use langSami*/ + langSaamisk = 29, /* Use langSami */ + langFaeroese = 30, /* Use langFaroese */ + langIrish = 35, /* Use langIrishGaelic */ + langGalla = 87, /* Use langOromo */ + langAfricaans = 141, /* Use langAfrikaans */ + langGreekPoly = 148 /* Use langGreekAncient*/ +}; + +#endif /* __MacScriptExtracts__ */ diff --git a/source/XMPFiles/FormatSupport/PNG_Support.cpp b/source/XMPFiles/FormatSupport/PNG_Support.cpp index f074729..988edda 100644 --- a/source/XMPFiles/FormatSupport/PNG_Support.cpp +++ b/source/XMPFiles/FormatSupport/PNG_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/PNG_Support.hpp b/source/XMPFiles/FormatSupport/PNG_Support.hpp index 1a5aaae..b10f899 100644 --- a/source/XMPFiles/FormatSupport/PNG_Support.hpp +++ b/source/XMPFiles/FormatSupport/PNG_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp b/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp index f8e6290..0e57b49 100644 --- a/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp +++ b/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -20,26 +20,15 @@ // ================================================================================================= // IsMetadataImgRsrc // ================= -// -// The only image resources of possible interest as metadata have type '8BIM' and IDs: -// 1008, 1020, 1028, 1034, 1035, 1036, 1058, 1060, 1061 static inline bool IsMetadataImgRsrc ( XMP_Uns16 id ) { - - if ( (id < 1008) || (id > 1061) ) { - return false; - } else if ( id >= 1058 ) { - if ( id == 1059 ) return false; - } else if ( id > 1036 ) { - return false; - } else if ( id > 1028 ) { - if ( id < 1034 ) return false; - } else if ( id < 1028 ) { - if ( (id != 1008) && (id != 1020) ) return false; - } - - return true; + if ( id == 0 ) return false; + + int i; + for ( i = 0; id < kPSIR_MetadataIDs[i]; ++i ) {} + if ( id == kPSIR_MetadataIDs[i] ) return true; + return false; } // IsMetadataImgRsrc @@ -97,9 +86,9 @@ bool PSIR_FileWriter::GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const { InternalRsrcMap::const_iterator rsrcPos = this->imgRsrcs.find ( id ); if ( rsrcPos == this->imgRsrcs.end() ) return false; - + const InternalRsrcInfo & rsrcInfo = rsrcPos->second; - + if ( info != 0 ) { info->id = rsrcInfo.id; info->dataLen = rsrcInfo.dataLen; @@ -108,7 +97,7 @@ bool PSIR_FileWriter::GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const } return true; - + } // PSIR_FileWriter::GetImgRsrc // ================================================================================================= @@ -126,7 +115,7 @@ void PSIR_FileWriter::SetImgRsrc ( XMP_Uns16 id, const void* clientPtr, XMP_Uns3 InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, length, this->fileParsed ) ); rsrcPos = this->imgRsrcs.insert ( rsrcPos, mapValue ); rsrcPtr = &rsrcPos->second; - + } else { rsrcPtr = &rsrcPos->second; @@ -136,12 +125,12 @@ void PSIR_FileWriter::SetImgRsrc ( XMP_Uns16 id, const void* clientPtr, XMP_Uns3 (memcmp ( rsrcPtr->dataPtr, clientPtr, length ) == 0) ) { return; } - + rsrcPtr->FreeData(); // Release any existing data allocation. rsrcPtr->dataLen = length; // And this might be changing. } - + rsrcPtr->changed = true; rsrcPtr->dataPtr = malloc ( length ); if ( rsrcPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); @@ -159,7 +148,7 @@ void PSIR_FileWriter::DeleteImgRsrc ( XMP_Uns16 id ) { InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id ); if ( rsrcPos == this->imgRsrcs.end() ) return; // Nothing to delete. - + this->imgRsrcs.erase ( id ); this->changed = true; if ( id != kPSIR_XMP ) this->legacyDeleted = true; @@ -175,7 +164,7 @@ bool PSIR_FileWriter::IsLegacyChanged() if ( ! this->changed ) return false; if ( this->legacyDeleted ) return true; - + InternalRsrcMap::iterator irPos = this->imgRsrcs.begin(); InternalRsrcMap::iterator irEnd = this->imgRsrcs.end(); @@ -185,7 +174,7 @@ bool PSIR_FileWriter::IsLegacyChanged() } return false; // Can get here if the XMP is the only thing changed. - + } // PSIR_FileWriter::IsLegacyChanged // ================================================================================================= @@ -199,7 +188,7 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, if ( length == 0 ) return; // Allocate space for the full in-memory data and copy it. - + if ( ! copyData ) { this->memContent = (XMP_Uns8*) data; XMP_Assert ( ! this->ownedContent ); @@ -211,15 +200,15 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, this->ownedContent = true; } this->memLength = length; - + // Capture the info for all of the resources. - + XMP_Uns8* psirPtr = this->memContent; XMP_Uns8* psirEnd = psirPtr + length; XMP_Uns8* psirLimit = psirEnd - kMinImgRsrcSize; - + while ( psirPtr <= psirLimit ) { - + XMP_Uns8* origin = psirPtr; // The beginning of this resource. XMP_Uns32 type = GetUns32BE(psirPtr); XMP_Uns16 id = GetUns16BE(psirPtr+4); @@ -228,7 +217,7 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, XMP_Uns8* namePtr = psirPtr; XMP_Uns16 nameLen = namePtr[0]; // ! The length for the Pascal string, w/ room for "+2". psirPtr += ((nameLen + 2) & 0xFFFE); // ! Round up to an even offset. Yes, +2! - + if ( psirPtr > psirEnd-4 ) break; // Bad image resource. Throw instead? XMP_Uns32 dataLen = GetUns32BE(psirPtr); @@ -236,9 +225,9 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, XMP_Uns32 dataOffset = (XMP_Uns32) ( psirPtr - this->memContent ); XMP_Uns8* nextRsrc = psirPtr + ((dataLen + 1) & 0xFFFFFFFEUL); // ! Round up to an even offset. - + if ( (dataLen > length) || (psirPtr > psirEnd-dataLen) ) break; // Bad image resource. Throw instead? - + if ( type == k8BIM ) { InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, dataLen, kIsMemoryBased ) ); InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.insert ( this->imgRsrcs.end(), mapValue ); @@ -252,9 +241,9 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, XMP_Assert ( (rsrcLength & 1) == 0 ); this->otherRsrcs.push_back ( OtherRsrcInfo ( rsrcOffset, rsrcLength ) ); } - + psirPtr = nextRsrc; - + } } // PSIR_FileWriter::ParseMemoryResources @@ -266,28 +255,28 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length ) { bool ok; - + this->DeleteExistingInfo(); this->fileParsed = true; if ( length == 0 ) return; - + // Parse the image resource block. IOBuffer ioBuf; ioBuf.filePos = LFA_Seek ( fileRef, 0, SEEK_CUR ); - + XMP_Int64 psirOrigin = ioBuf.filePos; // Need this to determine the resource data offsets. XMP_Int64 fileEnd = ioBuf.filePos + length; - + std::string rsrcPName; - + while ( (ioBuf.filePos + (ioBuf.ptr - ioBuf.data)) < fileEnd ) { - + ok = CheckFileSpace ( fileRef, &ioBuf, 12 ); // The minimal image resource takes 12 bytes. if ( ! ok ) break; // Bad image resource. Throw instead? XMP_Int64 thisRsrcPos = ioBuf.filePos + (ioBuf.ptr - ioBuf.data); - + XMP_Uns32 type = GetUns32BE(ioBuf.ptr); XMP_Uns16 id = GetUns16BE(ioBuf.ptr+4); ioBuf.ptr += 6; // Advance to the resource name. @@ -296,7 +285,7 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length XMP_Uns16 paddedLen = (nameLen + 2) & 0xFFFE; // ! Round up to an even total. Yes, +2! ok = CheckFileSpace ( fileRef, &ioBuf, paddedLen+4 ); // Get the name text and the data length. if ( ! ok ) break; // Bad image resource. Throw instead? - + if ( nameLen > 0 ) rsrcPName.assign ( (char*)(ioBuf.ptr), paddedLen ); // ! Include the length byte and pad. ioBuf.ptr += paddedLen; // Move to the data length. @@ -317,7 +306,7 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, dataLen, kIsFileBased ) ); InternalRsrcMap::iterator newRsrc = this->imgRsrcs.insert ( this->imgRsrcs.end(), mapValue ); InternalRsrcInfo* rsrcPtr = &newRsrc->second; - + rsrcPtr->origOffset = (XMP_Uns32)thisDataPos; if ( nameLen > 0 ) { @@ -325,7 +314,7 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length if ( rsrcPtr->rsrcName == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); memcpy ( (void*)rsrcPtr->rsrcName, rsrcPName.c_str(), paddedLen ); // AUDIT: Safe, allocated enough bytes above. } - + if ( ! IsMetadataImgRsrc ( id ) ) { MoveToOffset ( fileRef, nextRsrcPos, &ioBuf ); continue; @@ -346,9 +335,9 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length LFA_Read ( fileRef, (void*)rsrcPtr->dataPtr, dataLen ); FillBuffer ( fileRef, nextRsrcPos, &ioBuf ); } - + } - + #if 0 { printf ( "\nPSIR_FileWriter::ParseFileResources, count = %d\n", this->imgRsrcs.size() ); @@ -362,7 +351,7 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length } } #endif - + } // PSIR_FileWriter::ParseFileResources // ================================================================================================= @@ -372,9 +361,9 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr ) { if ( this->fileParsed ) XMP_Throw ( "Not memory based", kXMPErr_EnforceFailure ); - + // Compute the size and allocate the new image resource block. - + XMP_Uns32 newLength = 0; InternalRsrcMap::iterator irPos = this->imgRsrcs.begin(); @@ -391,27 +380,27 @@ XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr ) newLength += ((nameLen + 2) & 0xFFFFFFFEUL); // ! Yes, +2. } } - + for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) { // Add in the non-8BIM resources. newLength += this->otherRsrcs[i].rsrcLength; } - + XMP_Uns8* newContent = (XMP_Uns8*) malloc ( newLength ); if ( newContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); - + // Fill in the new image resource block. - + XMP_Uns8* rsrcPtr = newContent; for ( irPos = this->imgRsrcs.begin(); irPos != irEnd; ++irPos ) { // Do the 8BIM resources. const InternalRsrcInfo & rsrcInfo = irPos->second; - + PutUns32BE ( k8BIM, rsrcPtr ); rsrcPtr += 4; PutUns16BE ( rsrcInfo.id, rsrcPtr ); rsrcPtr += 2; - + if ( rsrcInfo.rsrcName == 0 ) { PutUns16BE ( 0, rsrcPtr ); rsrcPtr += 2; @@ -427,7 +416,7 @@ XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr ) ++rsrcPtr; } } - + PutUns32BE ( rsrcInfo.dataLen, rsrcPtr ); rsrcPtr += 4; if ( rsrcInfo.dataLen > (newLength - (rsrcPtr - newContent)) ) { @@ -451,17 +440,17 @@ XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr ) memcpy ( rsrcPtr, srcPtr, srcLen ); // AUDIT: Protected by the above check. rsrcPtr += srcLen; // No need to pad, included in the original resource length. } - + XMP_Assert ( rsrcPtr == (newContent + newLength) ); - + // Parse the rebuilt image resource block. This is the easiest way to reconstruct the map. - + this->ParseMemoryResources ( newContent, newLength, false ); this->ownedContent = (newLength > 0); // ! We really do own the new content, if not empty. - + if ( dataPtr != 0 ) *dataPtr = newContent; return newLength; - + } // PSIR_FileWriter::UpdateMemoryResources // ================================================================================================= @@ -473,20 +462,20 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File { IgnoreParam(ioBuf); const XMP_Uns32 zero32 = 0; - + const bool checkAbort = (abortProc != 0); - + struct RsrcHeader { XMP_Uns32 type; XMP_Uns16 id; }; XMP_Assert ( (offsetof(RsrcHeader,type) == 0) && (offsetof(RsrcHeader,id) == 4) ); - + if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure ); - + XMP_Int64 destLenOffset = LFA_Seek ( destRef, 0, SEEK_CUR ); XMP_Uns32 destLength = 0; - + LFA_Write ( destRef, &destLength, 4 ); // Write a placeholder for the new PSIR section length. #if 0 @@ -502,7 +491,7 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File } } #endif - + // First write all of the '8BIM' resources from the map. Use the internal data if present, else // copy the data from the file. @@ -516,11 +505,11 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File for ( ; rsrcPos != rsrcEnd; ++rsrcPos ) { InternalRsrcInfo& currRsrc = rsrcPos->second; - + outHeader.id = MakeUns16BE ( currRsrc.id ); LFA_Write ( destRef, &outHeader, 6 ); destLength += 6; - + if ( currRsrc.rsrcName == 0 ) { LFA_Write ( destRef, &zero32, 2 ); destLength += 2; @@ -531,7 +520,7 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File LFA_Write ( destRef, currRsrc.rsrcName, paddedLen ); destLength += paddedLen; } - + XMP_Uns32 dataLen = MakeUns32BE ( currRsrc.dataLen ); LFA_Write ( destRef, &dataLen, 4 ); // printf ( " #%d, offset %d (0x%X), dataLen %d\n", currRsrc.id, destLength, destLength, currRsrc.dataLen ); @@ -542,18 +531,18 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File LFA_Seek ( sourceRef, currRsrc.origOffset, SEEK_SET ); LFA_Copy ( sourceRef, destRef, currRsrc.dataLen ); } - + destLength += 4 + currRsrc.dataLen; - + if ( (currRsrc.dataLen & 1) != 0 ) { LFA_Write ( destRef, &zero32, 1 ); // ! Pad the data to an even length. ++destLength; } } - + // Now write all of the non-8BIM resources. Copy the entire resource chunk from the source file. - + // printf ( "\nPSIR_FileWriter::UpdateFileResources - other resources\n" ); for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) { // printf ( " offset %d (0x%X), length %d", @@ -570,7 +559,7 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File XMP_Uns32 outLen = MakeUns32BE ( destLength ); LFA_Write ( destRef, &outLen, 4 ); LFA_Seek ( destRef, 0, SEEK_END ); - + // *** Not rebuilding the internal map - turns out we never want it, why pay for the I/O. // *** Should probably add an option for all of these cases, memory and file based. diff --git a/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp b/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp index 592f3e2..c372dce 100644 --- a/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp +++ b/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/PSIR_Support.hpp b/source/XMPFiles/FormatSupport/PSIR_Support.hpp index a651bef..8b5c507 100644 --- a/source/XMPFiles/FormatSupport/PSIR_Support.hpp +++ b/source/XMPFiles/FormatSupport/PSIR_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -38,8 +38,6 @@ /// This is the case for all of the derived classes, even though the memory based ones happen to /// have all of the image resources in memory. Being "handled" means being in the image resource /// map used by GetImgRsrc. The handled image resources are: -/// \li 1008 - Ancient caption PString -/// \li 1020 - Ancient caption string /// \li 1028 - IPTC /// \li 1034 - Copyrighted flag /// \li 1035 - Copyright information URL @@ -51,7 +49,7 @@ /// packaged in a DLL by themselves. They do not provide any form of C++ ABI protection. // ================================================================================================= - + // These aren't inside PSIR_Manager because the static array can't be initialized there. enum { @@ -59,26 +57,23 @@ enum { kMinImgRsrcSize = 4+2+2+4 // The minimum size for an image resource. }; -enum { kPSIR_MetadataCount = 9 }; // 1 2 3 4 5 6 7 8 9 -static const XMP_Uns16 kPSIR_MetadataIDs[] = { 1008, 1020, 1028, 1034, 1035, 1036, 1058, 1060, 1061, 0 }; - enum { - kPSIR_OldCaptionPStr = 1008, - kPSIR_OldCaption = 1020, kPSIR_IPTC = 1028, kPSIR_CopyrightFlag = 1034, kPSIR_CopyrightURL = 1035, - kPSIR_Thumbnail = 1036, kPSIR_Exif = 1058, kPSIR_XMP = 1060, kPSIR_IPTCDigest = 1061 }; +enum { kPSIR_MetadataCount = 6 }; +static const XMP_Uns16 kPSIR_MetadataIDs[] = // ! Must be in descending order with 0 sentinel. + { kPSIR_IPTCDigest, kPSIR_XMP, kPSIR_Exif, kPSIR_CopyrightURL, kPSIR_CopyrightFlag, kPSIR_IPTC, 0 }; // ================================================================================================= // ================================================================================================= -// NOTE: Although Photoshop image resources have a type and ID, for metadatya we only care about +// NOTE: Although Photoshop image resources have a type and ID, for metadatya we only care about // those of type "8BIM". Resources of other types are preserved in files, but can't be individually // accessed through the PSIR_Manager API. @@ -101,10 +96,10 @@ public: ImgRsrcInfo ( XMP_Uns16 _id, XMP_Uns32 _dataLen, void* _dataPtr, XMP_Uns32 _origOffset ) : id(_id), dataLen(_dataLen), dataPtr(_dataPtr), origOffset(_origOffset) {}; }; - + // The origOffset is the absolute file offset for file parses, the memory block offset for // memory parses. It is the offset of the resource data portion, not the overall resource. - + // --------------------------------------------------------------------------------------------- // Get the information about a "handled" image resource. Returns false if the image resource is // not handled, even if it was present in the parsed input. @@ -113,20 +108,20 @@ public: // --------------------------------------------------------------------------------------------- // Set the value for an image resource. It can be any resource, even one not originally handled. - + virtual void SetImgRsrc ( XMP_Uns16 id, const void* dataPtr, XMP_Uns32 length ) = 0; // --------------------------------------------------------------------------------------------- // Delete an image resource. Does nothing if the image resource does not exist. - + virtual void DeleteImgRsrc ( XMP_Uns16 id ) = 0; // --------------------------------------------------------------------------------------------- // Determine if the image resources are changed. - + virtual bool IsChanged() = 0; virtual bool IsLegacyChanged() = 0; - + // --------------------------------------------------------------------------------------------- virtual void ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData = true ) = 0; @@ -138,7 +133,7 @@ public: // by \c UpdateMemoryResources must be treated as read only. It exists until the PSIR_Manager // destructor is called. UpdateMemoryResources can be used on a read-only instance to get the // raw data block info. - + virtual XMP_Uns32 UpdateMemoryResources ( void** dataPtr ) = 0; virtual XMP_Uns32 UpdateFileResources ( LFA_FileRef sourceRef, LFA_FileRef destRef, IOBuffer * ioBuf, XMP_AbortProc abortProc, void * abortArg ) = 0; @@ -166,9 +161,9 @@ class PSIR_MemoryReader : public PSIR_Manager { // The leaf class for memory-bas public: bool GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const; - + void SetImgRsrc ( XMP_Uns16 id, const void* dataPtr, XMP_Uns32 length ) { NotAppropriate(); }; - + void DeleteImgRsrc ( XMP_Uns16 id ) { NotAppropriate(); }; bool IsChanged() { return false; }; @@ -184,7 +179,7 @@ public: PSIR_MemoryReader() : ownedContent(false), psirLength(0), psirContent(0) {}; virtual ~PSIR_MemoryReader() { if ( this->ownedContent ) free ( this->psirContent ); }; - + private: // Memory usage notes: PSIR_MemoryReader is for memory-based read-only usage (both apply). There @@ -192,12 +187,12 @@ private: // PSIR stream. bool ownedContent; - + XMP_Uns32 psirLength; XMP_Uns8* psirContent; - + typedef std::map<XMP_Uns16,ImgRsrcInfo> ImgRsrcMap; - + ImgRsrcMap imgRsrcs; static inline void NotAppropriate() { XMP_Throw ( "Not appropriate for PSIR_Reader", kXMPErr_InternalFailure ); }; @@ -225,10 +220,10 @@ public: bool IsChanged() { return this->changed; }; bool IsLegacyChanged(); - + void ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData = true ); void ParseFileResources ( LFA_FileRef file, XMP_Uns32 length ); - + XMP_Uns32 UpdateMemoryResources ( void** dataPtr ); XMP_Uns32 UpdateFileResources ( LFA_FileRef sourceRef, LFA_FileRef destRef, IOBuffer * ioBuf, XMP_AbortProc abortProc, void * abortArg ); @@ -241,16 +236,16 @@ public: // Memory usage notes: PSIR_FileWriter is for file-based OR read/write usage. For memory-based // streams the dataPtr and rsrcName are initially into the stream, they become a separate // allocation if changed. For file-based streams they are always a separate allocation. - + // ! The working data values are always big endian, no matter where stored. It is the client's // ! responsibility to flip them as necessary. - + static const bool kIsFileBased = true; // For use in the InternalRsrcInfo constructor. static const bool kIsMemoryBased = false; struct InternalRsrcInfo { public: - + bool changed; bool fileBased; XMP_Uns16 id; @@ -270,7 +265,7 @@ public: if ( this->rsrcName != 0 ) { free ( this->rsrcName ); this->rsrcName = 0; } } } - + InternalRsrcInfo ( XMP_Uns16 _id, XMP_Uns32 _dataLen, bool _fileBased ) : changed(false), fileBased(_fileBased), id(_id), dataLen(_dataLen), dataPtr(0), origOffset(0), rsrcName(0) {}; @@ -289,9 +284,9 @@ public: InternalRsrcInfo() // Hidden on purpose, fileBased must be properly set. : changed(false), fileBased(false), id(0), dataLen(0), dataPtr(0), origOffset(0), rsrcName(0) {}; - + }; - + // The origOffset is the absolute file offset for file parses, the memory block offset for // memory parses. It is the offset of the resource data portion, not the overall resource. @@ -306,7 +301,7 @@ private: typedef std::map<XMP_Uns16,InternalRsrcInfo> InternalRsrcMap; InternalRsrcMap imgRsrcs; - + struct OtherRsrcInfo { // For the resources of types other than "8BIM". XMP_Uns32 rsrcOffset; // The offset of the resource origin, the type field. XMP_Uns32 rsrcLength; // The full length of the resource, offset to the next resource. @@ -315,7 +310,7 @@ private: : rsrcOffset(_rsrcOffset), rsrcLength(_rsrcLength) {}; }; std::vector<OtherRsrcInfo> otherRsrcs; - + void DeleteExistingInfo(); }; // PSIR_FileWriter diff --git a/source/XMPFiles/FormatSupport/QuickTime_Support.cpp b/source/XMPFiles/FormatSupport/QuickTime_Support.cpp index 8ba1221..31091ea 100644 --- a/source/XMPFiles/FormatSupport/QuickTime_Support.cpp +++ b/source/XMPFiles/FormatSupport/QuickTime_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2009 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -8,84 +8,1141 @@ // ================================================================================================= #include "XMP_Environment.h" -#if ! ( XMP_64 || XMP_UNIXBuild) // Closes at very bottom. + +#if XMP_MacBuild + #include "Script.h" +#else + #include "MacScriptExtracts.h" +#endif #include "QuickTime_Support.hpp" -#if XMP_MacBuild - #include <Movies.h> -#elif XMP_WinBuild - #include "QTML.h" - #include "Movies.h" +#include "UnicodeConversions.hpp" +#include "UnicodeInlines.incl_cpp" +#include "Reconcile_Impl.hpp" + +// ================================================================================================= + +static const char * kMacRomanUTF8 [128] = { // UTF-8 mappings for MacRoman 80..FF. + "\xC3\x84", "\xC3\x85", "\xC3\x87", "\xC3\x89", "\xC3\x91", "\xC3\x96", "\xC3\x9C", "\xC3\xA1", + "\xC3\xA0", "\xC3\xA2", "\xC3\xA4", "\xC3\xA3", "\xC3\xA5", "\xC3\xA7", "\xC3\xA9", "\xC3\xA8", + "\xC3\xAA", "\xC3\xAB", "\xC3\xAD", "\xC3\xAC", "\xC3\xAE", "\xC3\xAF", "\xC3\xB1", "\xC3\xB3", + "\xC3\xB2", "\xC3\xB4", "\xC3\xB6", "\xC3\xB5", "\xC3\xBA", "\xC3\xB9", "\xC3\xBB", "\xC3\xBC", + "\xE2\x80\xA0", "\xC2\xB0", "\xC2\xA2", "\xC2\xA3", "\xC2\xA7", "\xE2\x80\xA2", "\xC2\xB6", "\xC3\x9F", + "\xC2\xAE", "\xC2\xA9", "\xE2\x84\xA2", "\xC2\xB4", "\xC2\xA8", "\xE2\x89\xA0", "\xC3\x86", "\xC3\x98", + "\xE2\x88\x9E", "\xC2\xB1", "\xE2\x89\xA4", "\xE2\x89\xA5", "\xC2\xA5", "\xC2\xB5", "\xE2\x88\x82", "\xE2\x88\x91", + "\xE2\x88\x8F", "\xCF\x80", "\xE2\x88\xAB", "\xC2\xAA", "\xC2\xBA", "\xCE\xA9", "\xC3\xA6", "\xC3\xB8", + "\xC2\xBF", "\xC2\xA1", "\xC2\xAC", "\xE2\x88\x9A", "\xC6\x92", "\xE2\x89\x88", "\xE2\x88\x86", "\xC2\xAB", + "\xC2\xBB", "\xE2\x80\xA6", "\xC2\xA0", "\xC3\x80", "\xC3\x83", "\xC3\x95", "\xC5\x92", "\xC5\x93", + "\xE2\x80\x93", "\xE2\x80\x94", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\x98", "\xE2\x80\x99", "\xC3\xB7", "\xE2\x97\x8A", + "\xC3\xBF", "\xC5\xB8", "\xE2\x81\x84", "\xE2\x82\xAC", "\xE2\x80\xB9", "\xE2\x80\xBA", "\xEF\xAC\x81", "\xEF\xAC\x82", + "\xE2\x80\xA1", "\xC2\xB7", "\xE2\x80\x9A", "\xE2\x80\x9E", "\xE2\x80\xB0", "\xC3\x82", "\xC3\x8A", "\xC3\x81", + "\xC3\x8B", "\xC3\x88", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F", "\xC3\x8C", "\xC3\x93", "\xC3\x94", + "\xEF\xA3\xBF", "\xC3\x92", "\xC3\x9A", "\xC3\x9B", "\xC3\x99", "\xC4\xB1", "\xCB\x86", "\xCB\x9C", + "\xC2\xAF", "\xCB\x98", "\xCB\x99", "\xCB\x9A", "\xC2\xB8", "\xCB\x9D", "\xCB\x9B", "\xCB\x87" +}; + +static const XMP_Uns32 kMacRomanCP [128] = { // Unicode codepoints for MacRoman 80..FF. + 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, + 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, + 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, + 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, + 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, + 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, + 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, + 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, + 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, + 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, + 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, + 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, + 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, + 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, // ! U+F8FF is private use solid Apple icon. + 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7 +}; + +// ------------------------------------------------------------------------------------------------- + +static const XMP_Uns16 kMacLangToScript_0_94 [95] = { + + /* langEnglish (0) */ smRoman, + /* langFrench (1) */ smRoman, + /* langGerman (2) */ smRoman, + /* langItalian (3) */ smRoman, + /* langDutch (4) */ smRoman, + /* langSwedish (5) */ smRoman, + /* langSpanish (6) */ smRoman, + /* langDanish (7) */ smRoman, + /* langPortuguese (8) */ smRoman, + /* langNorwegian (9) */ smRoman, + + /* langHebrew (10) */ smHebrew, + /* langJapanese (11) */ smJapanese, + /* langArabic (12) */ smArabic, + /* langFinnish (13) */ smRoman, + /* langGreek (14) */ smRoman, + /* langIcelandic (15) */ smRoman, + /* langMaltese (16) */ smRoman, + /* langTurkish (17) */ smRoman, + /* langCroatian (18) */ smRoman, + /* langTradChinese (19) */ smTradChinese, + + /* langUrdu (20) */ smArabic, + /* langHindi (21) */ smDevanagari, + /* langThai (22) */ smThai, + /* langKorean (23) */ smKorean, + /* langLithuanian (24) */ smCentralEuroRoman, + /* langPolish (25) */ smCentralEuroRoman, + /* langHungarian (26) */ smCentralEuroRoman, + /* langEstonian (27) */ smCentralEuroRoman, + /* langLatvian (28) */ smCentralEuroRoman, + /* langSami (29) */ kNoMacScript, // ! Not known, missing from Apple comments. + + /* langFaroese (30) */ smRoman, + /* langFarsi (31) */ smArabic, + /* langRussian (32) */ smCyrillic, + /* langSimpChinese (33) */ smSimpChinese, + /* langFlemish (34) */ smRoman, + /* langIrishGaelic (35) */ smRoman, + /* langAlbanian (36) */ smRoman, + /* langRomanian (37) */ smRoman, + /* langCzech (38) */ smCentralEuroRoman, + /* langSlovak (39) */ smCentralEuroRoman, + + /* langSlovenian (40) */ smRoman, + /* langYiddish (41) */ smHebrew, + /* langSerbian (42) */ smCyrillic, + /* langMacedonian (43) */ smCyrillic, + /* langBulgarian (44) */ smCyrillic, + /* langUkrainian (45) */ smCyrillic, + /* langBelorussian (46) */ smCyrillic, + /* langUzbek (47) */ smCyrillic, + /* langKazakh (48) */ smCyrillic, + /* langAzerbaijani (49) */ smCyrillic, + + /* langAzerbaijanAr (50) */ smArabic, + /* langArmenian (51) */ smArmenian, + /* langGeorgian (52) */ smGeorgian, + /* langMoldavian (53) */ smCyrillic, + /* langKirghiz (54) */ smCyrillic, + /* langTajiki (55) */ smCyrillic, + /* langTurkmen (56) */ smCyrillic, + /* langMongolian (57) */ smMongolian, + /* langMongolianCyr (58) */ smCyrillic, + /* langPashto (59) */ smArabic, + + /* langKurdish (60) */ smArabic, + /* langKashmiri (61) */ smArabic, + /* langSindhi (62) */ smArabic, + /* langTibetan (63) */ smTibetan, + /* langNepali (64) */ smDevanagari, + /* langSanskrit (65) */ smDevanagari, + /* langMarathi (66) */ smDevanagari, + /* langBengali (67) */ smBengali, + /* langAssamese (68) */ smBengali, + /* langGujarati (69) */ smGujarati, + + /* langPunjabi (70) */ smGurmukhi, + /* langOriya (71) */ smOriya, + /* langMalayalam (72) */ smMalayalam, + /* langKannada (73) */ smKannada, + /* langTamil (74) */ smTamil, + /* langTelugu (75) */ smTelugu, + /* langSinhalese (76) */ smSinhalese, + /* langBurmese (77) */ smBurmese, + /* langKhmer (78) */ smKhmer, + /* langLao (79) */ smLao, + + /* langVietnamese (80) */ smVietnamese, + /* langIndonesian (81) */ smRoman, + /* langTagalog (82) */ smRoman, + /* langMalayRoman (83) */ smRoman, + /* langMalayArabic (84) */ smArabic, + /* langAmharic (85) */ smEthiopic, + /* langTigrinya (86) */ smEthiopic, + /* langOromo (87) */ smEthiopic, + /* langSomali (88) */ smRoman, + /* langSwahili (89) */ smRoman, + + /* langKinyarwanda (90) */ smRoman, + /* langRundi (91) */ smRoman, + /* langNyanja (92) */ smRoman, + /* langMalagasy (93) */ smRoman, + /* langEsperanto (94) */ smRoman + +}; // kMacLangToScript_0_94 + +static const XMP_Uns16 kMacLangToScript_128_151 [24] = { + + /* langWelsh (128) */ smRoman, + /* langBasque (129) */ smRoman, + + /* langCatalan (130) */ smRoman, + /* langLatin (131) */ smRoman, + /* langQuechua (132) */ smRoman, + /* langGuarani (133) */ smRoman, + /* langAymara (134) */ smRoman, + /* langTatar (135) */ smCyrillic, + /* langUighur (136) */ smArabic, + /* langDzongkha (137) */ smTibetan, + /* langJavaneseRom (138) */ smRoman, + /* langSundaneseRom (139) */ smRoman, + + /* langGalician (140) */ smRoman, + /* langAfrikaans (141) */ smRoman, + /* langBreton (142) */ smRoman, + /* langInuktitut (143) */ smEthiopic, + /* langScottishGaelic (144) */ smRoman, + /* langManxGaelic (145) */ smRoman, + /* langIrishGaelicScript (146) */ smRoman, + /* langTongan (147) */ smRoman, + /* langGreekAncient (148) */ smGreek, + /* langGreenlandic (149) */ smRoman, + + /* langAzerbaijanRoman (150) */ smRoman, + /* langNynorsk (151) */ smRoman + +}; // kMacLangToScript_128_151 + +// ------------------------------------------------------------------------------------------------- + +static const char * kMacToXMPLang_0_94 [95] = { + + /* langEnglish (0) */ "en", + /* langFrench (1) */ "fr", + /* langGerman (2) */ "de", + /* langItalian (3) */ "it", + /* langDutch (4) */ "nl", + /* langSwedish (5) */ "sv", + /* langSpanish (6) */ "es", + /* langDanish (7) */ "da", + /* langPortuguese (8) */ "pt", + /* langNorwegian (9) */ "no", + + /* langHebrew (10) */ "he", + /* langJapanese (11) */ "ja", + /* langArabic (12) */ "ar", + /* langFinnish (13) */ "fi", + /* langGreek (14) */ "el", + /* langIcelandic (15) */ "is", + /* langMaltese (16) */ "mt", + /* langTurkish (17) */ "tr", + /* langCroatian (18) */ "hr", + /* langTradChinese (19) */ "zh", + + /* langUrdu (20) */ "ur", + /* langHindi (21) */ "hi", + /* langThai (22) */ "th", + /* langKorean (23) */ "ko", + /* langLithuanian (24) */ "lt", + /* langPolish (25) */ "pl", + /* langHungarian (26) */ "hu", + /* langEstonian (27) */ "et", + /* langLatvian (28) */ "lv", + /* langSami (29) */ "se", + + /* langFaroese (30) */ "fo", + /* langFarsi (31) */ "fa", + /* langRussian (32) */ "ru", + /* langSimpChinese (33) */ "zh", + /* langFlemish (34) */ "nl", + /* langIrishGaelic (35) */ "ga", + /* langAlbanian (36) */ "sq", + /* langRomanian (37) */ "ro", + /* langCzech (38) */ "cs", + /* langSlovak (39) */ "sk", + + /* langSlovenian (40) */ "sl", + /* langYiddish (41) */ "yi", + /* langSerbian (42) */ "sr", + /* langMacedonian (43) */ "mk", + /* langBulgarian (44) */ "bg", + /* langUkrainian (45) */ "uk", + /* langBelorussian (46) */ "be", + /* langUzbek (47) */ "uz", + /* langKazakh (48) */ "kk", + /* langAzerbaijani (49) */ "az", + + /* langAzerbaijanAr (50) */ "az", + /* langArmenian (51) */ "hy", + /* langGeorgian (52) */ "ka", + /* langMoldavian (53) */ "ro", + /* langKirghiz (54) */ "ky", + /* langTajiki (55) */ "tg", + /* langTurkmen (56) */ "tk", + /* langMongolian (57) */ "mn", + /* langMongolianCyr (58) */ "mn", + /* langPashto (59) */ "ps", + + /* langKurdish (60) */ "ku", + /* langKashmiri (61) */ "ks", + /* langSindhi (62) */ "sd", + /* langTibetan (63) */ "bo", + /* langNepali (64) */ "ne", + /* langSanskrit (65) */ "sa", + /* langMarathi (66) */ "mr", + /* langBengali (67) */ "bn", + /* langAssamese (68) */ "as", + /* langGujarati (69) */ "gu", + + /* langPunjabi (70) */ "pa", + /* langOriya (71) */ "or", + /* langMalayalam (72) */ "ml", + /* langKannada (73) */ "kn", + /* langTamil (74) */ "ta", + /* langTelugu (75) */ "te", + /* langSinhalese (76) */ "si", + /* langBurmese (77) */ "my", + /* langKhmer (78) */ "km", + /* langLao (79) */ "lo", + + /* langVietnamese (80) */ "vi", + /* langIndonesian (81) */ "id", + /* langTagalog (82) */ "tl", + /* langMalayRoman (83) */ "ms", + /* langMalayArabic (84) */ "ms", + /* langAmharic (85) */ "am", + /* langTigrinya (86) */ "ti", + /* langOromo (87) */ "om", + /* langSomali (88) */ "so", + /* langSwahili (89) */ "sw", + + /* langKinyarwanda (90) */ "rw", + /* langRundi (91) */ "rn", + /* langNyanja (92) */ "ny", + /* langMalagasy (93) */ "mg", + /* langEsperanto (94) */ "eo" + +}; // kMacToXMPLang_0_94 + +static const char * kMacToXMPLang_128_151 [24] = { + + /* langWelsh (128) */ "cy", + /* langBasque (129) */ "eu", + + /* langCatalan (130) */ "ca", + /* langLatin (131) */ "la", + /* langQuechua (132) */ "qu", + /* langGuarani (133) */ "gn", + /* langAymara (134) */ "ay", + /* langTatar (135) */ "tt", + /* langUighur (136) */ "ug", + /* langDzongkha (137) */ "dz", + /* langJavaneseRom (138) */ "jv", + /* langSundaneseRom (139) */ "su", + + /* langGalician (140) */ "gl", + /* langAfrikaans (141) */ "af", + /* langBreton (142) */ "br", + /* langInuktitut (143) */ "iu", + /* langScottishGaelic (144) */ "gd", + /* langManxGaelic (145) */ "gv", + /* langIrishGaelicScript (146) */ "ga", + /* langTongan (147) */ "to", + /* langGreekAncient (148) */ "", // ! Has no ISO 639-1 2 letter code. + /* langGreenlandic (149) */ "kl", + + /* langAzerbaijanRoman (150) */ "az", + /* langNynorsk (151) */ "nn" + +}; // kMacToXMPLang_128_151 + +// ------------------------------------------------------------------------------------------------- + +#if XMP_WinBuild + +static UINT kMacScriptToWinCP[34] = { + /* smRoman (0) */ 10000, // There don't seem to be symbolic constants. + /* smJapanese (1) */ 10001, // From http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx + /* smTradChinese (2) */ 10002, + /* smKorean (3) */ 10003, + /* smArabic (4) */ 10004, + /* smHebrew (5) */ 10005, + /* smGreek (6) */ 10006, + /* smCyrillic (7) */ 10007, + /* smRSymbol (8) */ 0, + /* smDevanagari (9) */ 0, + /* smGurmukhi (10) */ 0, + /* smGujarati (11) */ 0, + /* smOriya (12) */ 0, + /* smBengali (13) */ 0, + /* smTamil (14) */ 0, + /* smTelugu (15) */ 0, + /* smKannada (16) */ 0, + /* smMalayalam (17) */ 0, + /* smSinhalese (18) */ 0, + /* smBurmese (19) */ 0, + /* smKhmer (20) */ 0, + /* smThai (21) */ 10021, + /* smLao (22) */ 0, + /* smGeorgian (23) */ 0, + /* smArmenian (24) */ 0, + /* smSimpChinese (25) */ 10008, + /* smTibetan (26) */ 0, + /* smMongolian (27) */ 0, + /* smEthiopic (28) */ 0, + /* smGeez (28) */ 0, + /* smCentralEuroRoman (29) */ 10029, + /* smVietnamese (30) */ 0, + /* smExtArabic (31) */ 0, + /* smUninterp (32) */ 0 +}; // kMacScriptToWinCP + +static UINT kMacToWinCP_0_94 [95] = { + + /* langEnglish (0) */ 0, + /* langFrench (1) */ 0, + /* langGerman (2) */ 0, + /* langItalian (3) */ 0, + /* langDutch (4) */ 0, + /* langSwedish (5) */ 0, + /* langSpanish (6) */ 0, + /* langDanish (7) */ 0, + /* langPortuguese (8) */ 0, + /* langNorwegian (9) */ 0, + + /* langHebrew (10) */ 10005, + /* langJapanese (11) */ 10001, + /* langArabic (12) */ 10004, + /* langFinnish (13) */ 0, + /* langGreek (14) */ 10006, + /* langIcelandic (15) */ 10079, + /* langMaltese (16) */ 0, + /* langTurkish (17) */ 10081, + /* langCroatian (18) */ 10082, + /* langTradChinese (19) */ 10002, + + /* langUrdu (20) */ 0, + /* langHindi (21) */ 0, + /* langThai (22) */ 10021, + /* langKorean (23) */ 10003, + /* langLithuanian (24) */ 0, + /* langPolish (25) */ 0, + /* langHungarian (26) */ 0, + /* langEstonian (27) */ 0, + /* langLatvian (28) */ 0, + /* langSami (29) */ 0, + + /* langFaroese (30) */ 0, + /* langFarsi (31) */ 0, + /* langRussian (32) */ 0, + /* langSimpChinese (33) */ 10008, + /* langFlemish (34) */ 0, + /* langIrishGaelic (35) */ 0, + /* langAlbanian (36) */ 0, + /* langRomanian (37) */ 10010, + /* langCzech (38) */ 0, + /* langSlovak (39) */ 0, + + /* langSlovenian (40) */ 0, + /* langYiddish (41) */ 0, + /* langSerbian (42) */ 0, + /* langMacedonian (43) */ 0, + /* langBulgarian (44) */ 0, + /* langUkrainian (45) */ 10017, + /* langBelorussian (46) */ 0, + /* langUzbek (47) */ 0, + /* langKazakh (48) */ 0, + /* langAzerbaijani (49) */ 0, + + /* langAzerbaijanAr (50) */ 0, + /* langArmenian (51) */ 0, + /* langGeorgian (52) */ 0, + /* langMoldavian (53) */ 0, + /* langKirghiz (54) */ 0, + /* langTajiki (55) */ 0, + /* langTurkmen (56) */ 0, + /* langMongolian (57) */ 0, + /* langMongolianCyr (58) */ 0, + /* langPashto (59) */ 0, + + /* langKurdish (60) */ 0, + /* langKashmiri (61) */ 0, + /* langSindhi (62) */ 0, + /* langTibetan (63) */ 0, + /* langNepali (64) */ 0, + /* langSanskrit (65) */ 0, + /* langMarathi (66) */ 0, + /* langBengali (67) */ 0, + /* langAssamese (68) */ 0, + /* langGujarati (69) */ 0, + + /* langPunjabi (70) */ 0, + /* langOriya (71) */ 0, + /* langMalayalam (72) */ 0, + /* langKannada (73) */ 0, + /* langTamil (74) */ 0, + /* langTelugu (75) */ 0, + /* langSinhalese (76) */ 0, + /* langBurmese (77) */ 0, + /* langKhmer (78) */ 0, + /* langLao (79) */ 0, + + /* langVietnamese (80) */ 0, + /* langIndonesian (81) */ 0, + /* langTagalog (82) */ 0, + /* langMalayRoman (83) */ 0, + /* langMalayArabic (84) */ 0, + /* langAmharic (85) */ 0, + /* langTigrinya (86) */ 0, + /* langOromo (87) */ 0, + /* langSomali (88) */ 0, + /* langSwahili (89) */ 0, + + /* langKinyarwanda (90) */ 0, + /* langRundi (91) */ 0, + /* langNyanja (92) */ 0, + /* langMalagasy (93) */ 0, + /* langEsperanto (94) */ 0 + +}; // kMacToWinCP_0_94 + #endif -namespace QuickTime_Support +// ================================================================================================= +// GetMacScript +// ============ + +static XMP_Uns16 GetMacScript ( XMP_Uns16 macLang ) { + XMP_Uns16 macScript = kNoMacScript; + + if ( macLang <= 94 ) { + macScript = kMacLangToScript_0_94[macLang]; + } else if ( (128 <= macLang) && (macLang <= 151) ) { + macScript = kMacLangToScript_0_94[macLang-128]; + } + + return macScript; + +} // GetMacScript + +// ================================================================================================= +// GetWinCP +// ======== + +#if XMP_WinBuild + +static UINT GetWinCP ( XMP_Uns16 macLang ) +{ + UINT winCP = 0; + + if ( macLang <= 94 ) winCP = kMacToWinCP_0_94[macLang]; + + if ( winCP == 0 ) { + XMP_Uns16 macScript = GetMacScript ( macLang ); + if ( macScript != kNoMacScript ) winCP = kMacScriptToWinCP[macScript]; + } + + return winCP; + +} // GetWinCP + +#endif + +// ================================================================================================= +// GetXMPLang +// ========== + +static XMP_StringPtr GetXMPLang ( XMP_Uns16 macLang ) +{ + XMP_StringPtr xmpLang = ""; + + if ( macLang <= 94 ) { + xmpLang = kMacToXMPLang_0_94[macLang]; + } else if ( (128 <= macLang) && (macLang <= 151) ) { + xmpLang = kMacToXMPLang_128_151[macLang-128]; + } + + return xmpLang; + +} // GetXMPLang + +// ================================================================================================= +// GetMacLang +// ========== + +static XMP_Uns16 GetMacLang ( std::string * xmpLang ) +{ + if ( *xmpLang == "" ) return kNoMacLang; + + size_t hyphenPos = xmpLang->find ( '-' ); // Make sure the XMP language is "generic". + if ( hyphenPos != std::string::npos ) xmpLang->erase ( hyphenPos ); + + for ( XMP_Uns16 i = 0; i <= 94; ++i ) { // Using std::map would be faster. + if ( *xmpLang == kMacToXMPLang_0_94[i] ) return i; + } + + for ( XMP_Uns16 i = 128; i <= 151; ++i ) { // Using std::map would be faster. + if ( *xmpLang == kMacToXMPLang_128_151[i-128] ) return i; + } + + return kNoMacLang; + +} // GetMacLang + +// ================================================================================================= +// MacRomanToUTF8 +// ============== - bool sMainInitOK = false; +static void MacRomanToUTF8 ( const std::string & macRoman, std::string * utf8 ) +{ + utf8->erase(); + + for ( XMP_Uns8* chPtr = (XMP_Uns8*)macRoman.c_str(); *chPtr != 0; ++chPtr ) { // ! Don't trust that char is unsigned. + if ( *chPtr < 0x80 ) { + (*utf8) += (char)*chPtr; + } else { + (*utf8) += kMacRomanUTF8[(*chPtr)-0x80]; + } + } + +} // MacRomanToUTF8 + +// ================================================================================================= +// UTF8ToMacRoman +// ============== + +static void UTF8ToMacRoman ( const std::string & utf8, std::string * macRoman ) +{ + macRoman->erase(); + bool inNonMRSpan = false; + + for ( const XMP_Uns8 * chPtr = (XMP_Uns8*)utf8.c_str(); *chPtr != 0; ++chPtr ) { // ! Don't trust that char is unsigned. + if ( *chPtr < 0x80 ) { + (*macRoman) += (char)*chPtr; + inNonMRSpan = false; + } else { + XMP_Uns32 cp = GetCodePoint ( &chPtr ); + --chPtr; // Make room for the loop increment. + XMP_Uns8 mr; + for ( mr = 0; (mr < 0x80) && (cp != kMacRomanCP[mr]); ++mr ) {}; // Using std::map would be faster. + if ( mr < 0x80 ) { + (*macRoman) += (char)(mr+0x80); + inNonMRSpan = false; + } else if ( ! inNonMRSpan ) { + (*macRoman) += '?'; + inNonMRSpan = true; + } + } + } + +} // UTF8ToMacRoman + +// ================================================================================================= +// IsMacLangKnown +// ============== + +static inline bool IsMacLangKnown ( XMP_Uns16 macLang ) +{ + XMP_Uns16 macScript = GetMacScript ( macLang ); + if ( macScript == kNoMacScript ) return false; + + #if XMP_UNIXBuild + if ( macScript != smRoman ) return false; + #elif XMP_WinBuild + if ( GetWinCP(macLang) == 0 ) return false; + #endif + + return true; + +} // IsMacLangKnown + +// ================================================================================================= +// ConvertToMacLang +// ================ + +bool ConvertToMacLang ( const std::string & utf8Value, XMP_Uns16 macLang, std::string * macValue ) +{ + macValue->erase(); + if ( macLang == kNoMacLang ) macLang = 0; // *** Zero is English, ought to use the "active" OS lang. + if ( ! IsMacLangKnown ( macLang ) ) return false; + + #if XMP_MacBuild + XMP_Uns16 macScript = GetMacScript ( macLang ); + ReconcileUtils::UTF8ToMacEncoding ( macScript, macLang, (XMP_Uns8*)utf8Value.c_str(), utf8Value.size(), macValue ); + #elif XMP_UNIXBuild + UTF8ToMacRoman ( utf8Value, macValue ); + #elif XMP_WinBuild + UINT winCP = GetWinCP ( macLang ); + ReconcileUtils::UTF8ToWinEncoding ( winCP, (XMP_Uns8*)utf8Value.c_str(), utf8Value.size(), macValue ); + #endif + + return true; + +} // ConvertToMacLang + +// ================================================================================================= +// ConvertFromMacLang +// ================== + +bool ConvertFromMacLang ( const std::string & macValue, XMP_Uns16 macLang, std::string * utf8Value ) +{ + utf8Value->erase(); + if ( ! IsMacLangKnown ( macLang ) ) return false; + + #if XMP_MacBuild + XMP_Uns16 macScript = GetMacScript ( macLang ); + ReconcileUtils::MacEncodingToUTF8 ( macScript, macLang, (XMP_Uns8*)macValue.c_str(), macValue.size(), utf8Value ); + #elif XMP_UNIXBuild + MacRomanToUTF8 ( macValue, utf8Value ); + #elif XMP_WinBuild + UINT winCP = GetWinCP ( macLang ); + ReconcileUtils::WinEncodingToUTF8 ( winCP, (XMP_Uns8*)macValue.c_str(), macValue.size(), utf8Value ); + #endif + + return true; - // ============================================================================================= +} // ConvertFromMacLang - bool MainInitialize ( bool ignoreInit ) - { - OSStatus err = noErr; +// ================================================================================================= +// ================================================================================================= +// TradQT_Manager +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// TradQT_Manager::ParseCachedBoxes +// ================================ +// +// Parse the cached '©...' children of the 'moov'/'udta' box. The contents of each cached box are +// a sequence of "mini boxes" analogous to XMP AltText arrays. Each mini box has a 16-bit size, +// 16-bit language code, and text. The size is only the text size. The language codes are Macintosh +// Script Manager langXyz codes. The text encoding is implicit in the language, see comments in +// Apple's Script.h header. + +bool TradQT_Manager::ParseCachedBoxes ( const MOOV_Manager & moovMgr ) +{ + MOOV_Manager::BoxInfo udtaInfo; + MOOV_Manager::BoxRef udtaRef = moovMgr.GetBox ( "moov/udta", &udtaInfo ); + if ( udtaRef == 0 ) return false; + + for ( XMP_Uns32 i = 0; i < udtaInfo.childCount; ++i ) { + + MOOV_Manager::BoxInfo currInfo; + MOOV_Manager::BoxRef currRef = moovMgr.GetNthChild ( udtaRef, i, &currInfo ); + if ( currRef == 0 ) break; // Sanity check, should not happen. + if ( (currInfo.boxType >> 24) != 0xA9 ) continue; + if ( currInfo.contentSize < 2+2+1 ) continue; // Want enough for a non-empty value. + + InfoMapPos newInfo = this->parsedBoxes.insert ( this->parsedBoxes.end(), + InfoMap::value_type ( currInfo.boxType, ParsedBoxInfo ( currInfo.boxType ) ) ); + std::vector<ValueInfo> * newValues = &newInfo->second.values; + + XMP_Uns8 * boxPtr = (XMP_Uns8*) currInfo.content; + XMP_Uns8 * boxEnd = boxPtr + currInfo.contentSize; + XMP_Uns16 miniLen, macLang; + + for ( ; boxPtr < boxEnd-4; boxPtr += miniLen ) { + + miniLen = 4 + GetUns16BE ( boxPtr ); // ! Include header in local miniLen. + macLang = GetUns16BE ( boxPtr+2); + if ( (miniLen <= 4) || (miniLen > (boxEnd - boxPtr)) ) continue; // Ignore bad or empty values. + + XMP_StringPtr valuePtr = (char*)(boxPtr+4); + size_t valueLen = miniLen - 4; + + newValues->push_back ( ValueInfo() ); + ValueInfo * newValue = &newValues->back(); + + // Only set the XMP language if the Mac script is known, i.e. the value can be converted. + + newValue->macLang = macLang; + if ( IsMacLangKnown ( macLang ) ) newValue->xmpLang = GetXMPLang ( macLang ); + newValue->macValue.assign ( valuePtr, valueLen ); + + } + + } + + return (! this->parsedBoxes.empty()); + +} // TradQT_Manager::ParseCachedBoxes + +// ================================================================================================= +// TradQT_Manager::ImportSimpleXMP +// =============================== +// +// Update a simple XMP property if the QT value looks newer. + +bool TradQT_Manager::ImportSimpleXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr prop ) const +{ + + try { + + InfoMapCPos infoPos = this->parsedBoxes.find ( id ); + if ( infoPos == this->parsedBoxes.end() ) return false; + if ( infoPos->second.values.empty() ) return false; + + std::string xmpValue, tempValue; + XMP_OptionBits flags; + bool xmpExists = xmp->GetProperty ( ns, prop, &xmpValue, &flags ); + if ( xmpExists && (! XMP_PropIsSimple ( flags )) ) { + XMP_Throw ( "TradQT_Manager::ImportSimpleXMP - XMP property must be simple", kXMPErr_BadParam ); + } + + bool convertOK; + const ValueInfo & qtItem = infoPos->second.values[0]; // ! Use the first QT entry. + + if ( xmpExists ) { + convertOK = ConvertToMacLang ( xmpValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + if ( tempValue == qtItem.macValue ) return false; // QT value matches back converted XMP value. + } + + convertOK = ConvertFromMacLang ( qtItem.macValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + xmp->SetProperty ( ns, prop, tempValue.c_str() ); + return true; + + } catch ( ... ) { + + return false; // Don't let one failure abort other imports. + + } + +} // TradQT_Manager::ImportSimpleXMP + +// ================================================================================================= +// TradQT_Manager::ImportLangItem +// ============================== +// +// Update a specific XMP AltText item if the QuickTime value looks newer. + +bool TradQT_Manager::ImportLangItem ( const ValueInfo & qtItem, SXMPMeta * xmp, + XMP_StringPtr ns, XMP_StringPtr langArray ) const +{ + + try { + + XMP_StringPtr genericLang, specificLang; + if ( qtItem.xmpLang[0] != 0 ) { + genericLang = qtItem.xmpLang; + specificLang = qtItem.xmpLang; + } else { + genericLang = ""; + specificLang = "x-default"; + } + + bool convertOK; + std::string xmpValue, tempValue, actualLang; + bool xmpExists = xmp->GetLocalizedText ( ns, langArray, genericLang, specificLang, &actualLang, &xmpValue, 0 ); + if ( xmpExists ) { + convertOK = ConvertToMacLang ( xmpValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + if ( tempValue == qtItem.macValue ) return true; // QT value matches back converted XMP value. + specificLang = actualLang.c_str(); + } + + convertOK = ConvertFromMacLang ( qtItem.macValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + xmp->SetLocalizedText ( ns, langArray, "", specificLang, tempValue.c_str() ); + return true; + + } catch ( ... ) { + + return false; // Don't let one failure abort other imports. + + } + +} // TradQT_Manager::ImportLangItem + +// ================================================================================================= +// TradQT_Manager::ImportLangAltXMP +// ================================ +// +// Update items in the XMP array if the QT value looks newer. + +bool TradQT_Manager::ImportLangAltXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const +{ + + try { + + InfoMapCPos infoPos = this->parsedBoxes.find ( id ); + if ( infoPos == this->parsedBoxes.end() ) return false; + if ( infoPos->second.values.empty() ) return false; // Quit now if there are no values. + + XMP_OptionBits flags; + bool xmpExists = xmp->GetProperty ( ns, langArray, 0, &flags ); + if ( ! xmpExists ) { + xmp->SetProperty ( ns, langArray, 0, kXMP_PropArrayIsAltText ); + } else if ( ! XMP_ArrayIsAltText ( flags ) ) { + XMP_Throw ( "TradQT_Manager::ImportLangAltXMP - XMP array must be AltText", kXMPErr_BadParam ); + } + + // Process all of the QT values, looking up the appropriate XMP language for each. - if ( ignoreInit ) { - sMainInitOK = true; - return true; + bool haveMappings = false; + const ValueVector & qtValues = infoPos->second.values; + + for ( size_t i = 0, limit = qtValues.size(); i < limit; ++i ) { + const ValueInfo & qtItem = qtValues[i]; + if ( *qtItem.xmpLang == 0 ) continue; // Only do known mappings in the loop. + haveMappings |= this->ImportLangItem ( qtItem, xmp, ns, langArray ); + } + + if ( ! haveMappings ) { + // If nothing mapped, process the first QT item to XMP's "x-default". + haveMappings = this->ImportLangItem ( qtValues[0], xmp, ns, langArray ); // ! No xmpLang implies "x-default". } - #if XMP_WinBuild - err = ::InitializeQTML ( 0 ); - #endif + return haveMappings; + + } catch ( ... ) { + + return false; // Don't let one failure abort other imports. + + } + +} // TradQT_Manager::ImportLangAltXMP + +// ================================================================================================= +// TradQT_Manager::ExportSimpleXMP +// =============================== +// +// Export a simple XMP value to the first existing QuickTime item. Delete all of the QT values if the +// XMP value is empty or the XMP does not exist. + +// ! We don't create new QuickTime items since we don't know the language. + +void TradQT_Manager::ExportSimpleXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr prop, + bool createWithZeroLang /* = false */ ) +{ + std::string xmpValue, macValue; + + InfoMapPos infoPos = this->parsedBoxes.find ( id ); + bool qtFound = (infoPos != this->parsedBoxes.end()) && (! infoPos->second.values.empty()); - if ( err == noErr ) err = ::EnterMovies(); - if ( err == noErr ) sMainInitOK = true; + bool xmpFound = xmp.GetProperty ( ns, prop, &xmpValue, 0 ); + if ( (! xmpFound) || (xmpValue.empty()) ) { + if ( qtFound ) { + this->parsedBoxes.erase ( infoPos ); + this->changed = true; + } + return; + } + + XMP_Assert ( xmpFound ); + if ( ! qtFound ) { + if ( ! createWithZeroLang ) return; + infoPos = this->parsedBoxes.insert ( this->parsedBoxes.end(), + InfoMap::value_type ( id, ParsedBoxInfo ( id ) ) ); + ValueVector * newValues = &infoPos->second.values; + newValues->push_back ( ValueInfo() ); + ValueInfo * newValue = &newValues->back(); + newValue->macLang = 0; // Happens to be langEnglish. + newValue->xmpLang = kMacToXMPLang_0_94[0]; + this->changed = infoPos->second.changed = true; + } - return sMainInitOK; + ValueInfo * qtItem = &infoPos->second.values[0]; // ! Use the first QT entry. + if ( ! IsMacLangKnown ( qtItem->macLang ) ) return; - } // MainInitialize + bool convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue ); + if ( convertOK && (macValue != qtItem->macValue) ) { + qtItem->macValue = macValue; + this->changed = infoPos->second.changed = true; + } - // ============================================================================================= +} // TradQT_Manager::ExportSimpleXMP - void MainTerminate ( bool ignoreInit ) - { +// ================================================================================================= +// TradQT_Manager::ExportLangAltXMP +// ================================ +// +// Export XMP LangAlt array items to QuickTime, where the language and encoding mappings are known. +// If there are no known language and encoding mappings, map the XMP default item to the first +// existing QuickTime item. + +void TradQT_Manager::ExportLangAltXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) +{ + bool haveMappings = false; + std::string xmpPath, xmpValue, xmpLang, macValue; + + InfoMapPos infoPos = this->parsedBoxes.find ( id ); + if ( infoPos == this->parsedBoxes.end() ) { + infoPos = this->parsedBoxes.insert ( this->parsedBoxes.end(), + InfoMap::value_type ( id, ParsedBoxInfo ( id ) ) ); + } + + ValueVector * qtValues = &infoPos->second.values; + XMP_Index xmpCount = xmp.CountArrayItems ( ns, langArray ); + bool convertOK; + + if ( xmpCount == 0 ) { + // Delete the "mappable" QuickTime items if there are no XMP values. Leave the others alone. + for ( int i = (int)qtValues->size()-1; i > 0; --i ) { // ! Need a signed index. + if ( (*qtValues)[i].xmpLang[0] != 0 ) { + qtValues->erase ( qtValues->begin() + i ); + this->changed = infoPos->second.changed = true; + } + } + return; + } + + // Go through the XMP and look for a related macLang QuickTime item to update or create. + + for ( XMP_Index xmpIndex = 1; xmpIndex <= xmpCount; ++xmpIndex ) { // ! XMP index starts at 1! + + SXMPUtils::ComposeArrayItemPath ( ns, langArray, xmpIndex, &xmpPath ); + xmp.GetProperty ( ns, xmpPath.c_str(), &xmpValue, 0 ); + xmp.GetQualifier ( ns, xmpPath.c_str(), kXMP_NS_XML, "lang", &xmpLang, 0 ); + if ( xmpLang == "x-default" ) continue; + + XMP_Uns16 macLang = GetMacLang ( &xmpLang ); + if ( macLang == kNoMacLang ) continue; + + size_t qtIndex, qtLimit; + for ( qtIndex = 0, qtLimit = qtValues->size(); qtIndex < qtLimit; ++qtIndex ) { + if ( (*qtValues)[qtIndex].macLang == macLang ) break; + } - if ( ignoreInit ) return; + if ( qtIndex == qtLimit ) { + // No existing QuickTime item, try to create one. + if ( ! IsMacLangKnown ( macLang ) ) continue; + qtValues->push_back ( ValueInfo() ); + qtIndex = qtValues->size() - 1; + ValueInfo * newItem = &((*qtValues)[qtIndex]); + newItem->macLang = macLang; + newItem->xmpLang = GetXMPLang ( macLang ); // ! Use the 2 character root language. + } - ::ExitMovies(); + ValueInfo * qtItem = &((*qtValues)[qtIndex]); + qtItem->marked = true; // Mark it whether updated or not, don't delete it in the next pass. - #if XMP_WinBuild - ::TerminateQTML(); - #endif + convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue ); + if ( convertOK && (macValue != qtItem->macValue) ) { + qtItem->macValue.swap ( macValue ); // No need to make a copy. + haveMappings = true; + } + + } + this->changed |= haveMappings; + infoPos->second.changed |= haveMappings; - } // MainTerminate + // Go through the QuickTime items that are unmarked and delete those that have an xmpLang + // and known macScript. Clear all marks. + + for ( int i = (int)qtValues->size()-1; i > 0; --i ) { // ! Need a signed index. + ValueInfo * qtItem = &((*qtValues)[i]); + if ( qtItem->marked ) { + qtItem->marked = false; + } else if ( (qtItem->xmpLang[0] != 0) && IsMacLangKnown ( qtItem->macLang ) ) { + qtValues->erase ( qtValues->begin() + i ); + this->changed = infoPos->second.changed = true; + } + } - // ============================================================================================= + // If there were no mappings, export the XMP default item to the first QT item. - bool ThreadInitialize() - { - OSStatus err = noErr; + if ( (! haveMappings) && (! qtValues->empty()) ) { + + bool ok = xmp.GetLocalizedText ( ns, langArray, "", "x-default", 0, &xmpValue, 0 ); + if ( ! ok ) return; - #if XMP_MacBuild - err = ::EnterMoviesOnThread ( 0 ); - #endif + ValueInfo * qtItem = &((*qtValues)[0]); + if ( ! IsMacLangKnown ( qtItem->macLang ) ) return; - return (err == noErr); + convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue ); + if ( convertOK && (macValue != qtItem->macValue) ) { + qtItem->macValue.swap ( macValue ); // No need to make a copy. + this->changed = infoPos->second.changed = true; + } + + } + +} // TradQT_Manager::ExportLangAltXMP + +// ================================================================================================= +// TradQT_Manager::UpdateChangedBoxes +// ================================== + +void TradQT_Manager::UpdateChangedBoxes ( MOOV_Manager * moovMgr ) +{ + MOOV_Manager::BoxInfo udtaInfo; + MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo ); + XMP_Assert ( (udtaRef != 0) || (udtaInfo.childCount == 0) ); + + if ( udtaRef != 0 ) { // Might not have been a moov/udta box in the parse. + + // First go through the moov/udta/©... children and delete those that are not in the map. + + for ( XMP_Uns32 ordinal = udtaInfo.childCount; ordinal > 0; --ordinal ) { // ! Go backwards because of deletions. + + MOOV_Manager::BoxInfo currInfo; + MOOV_Manager::BoxRef currRef = moovMgr->GetNthChild ( udtaRef, (ordinal-1), &currInfo ); + if ( currRef == 0 ) break; // Sanity check, should not happen. + if ( (currInfo.boxType >> 24) != 0xA9 ) continue; + if ( currInfo.contentSize < 2+2+1 ) continue; // These were skipped by ParseCachedBoxes. + + InfoMapPos infoPos = this->parsedBoxes.find ( currInfo.boxType ); + if ( infoPos == this->parsedBoxes.end() ) moovMgr->DeleteNthChild ( udtaRef, (ordinal-1) ); - } // ThreadInitialize + } - // ============================================================================================= + } + + // Now go through the changed items in the map and update them in the moov/udta subtree. + + InfoMapCPos infoPos = this->parsedBoxes.begin(); + InfoMapCPos infoEnd = this->parsedBoxes.end(); + + for ( ; infoPos != infoEnd; ++infoPos ) { - void ThreadTerminate() - { + ParsedBoxInfo * qtItem = (ParsedBoxInfo*) &infoPos->second; + if ( ! qtItem->changed ) continue; + qtItem->changed = false; + + XMP_Uns32 qtTotalSize = 0; // Total size of the QT values, ignoring empty values. + for ( size_t i = 0, limit = qtItem->values.size(); i < limit; ++i ) { + if ( ! qtItem->values[i].macValue.empty() ) { + if ( qtItem->values[i].macValue.size() > 0xFFFF ) qtItem->values[i].macValue.erase ( 0xFFFF ); + qtTotalSize += (XMP_Uns32)(2+2 + qtItem->values[i].macValue.size()); + } + } + + if ( udtaRef == 0 ) { // Might not have been a moov/udta box in the parse. + moovMgr->SetBox ( "moov/udta", 0, 0 ); + udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo ); + XMP_Assert ( udtaRef != 0 ); + } + + if ( qtTotalSize == 0 ) { - #if XMP_MacBuild - ::ExitMoviesOnThread(); - #endif + moovMgr->DeleteTypeChild ( udtaRef, qtItem->id ); - } // ThreadTerminate + } else { + + // Compose the complete box content. + + RawDataBlock fullValue; + fullValue.assign ( qtTotalSize, 0 ); + XMP_Uns8 * valuePtr = &fullValue[0]; + + for ( size_t i = 0, limit = qtItem->values.size(); i < limit; ++i ) { + XMP_Assert ( qtItem->values[i].macValue.size() <= 0xFFFF ); + XMP_Uns16 textSize = (XMP_Uns16)qtItem->values[i].macValue.size(); + if ( textSize == 0 ) continue; + PutUns16BE ( textSize, valuePtr ); valuePtr += 2; + PutUns16BE ( qtItem->values[i].macLang, valuePtr ); valuePtr += 2; + memcpy ( valuePtr, qtItem->values[i].macValue.c_str(), textSize ); valuePtr += textSize; + } + + // Look for an existing box to update, else add a new one. -} // namespace QuickTime_Support + MOOV_Manager::BoxInfo itemInfo; + MOOV_Manager::BoxRef itemRef = moovMgr->GetTypeChild ( udtaRef, qtItem->id, &itemInfo ); + + if ( itemRef != 0 ) { + moovMgr->SetBox ( itemRef, &fullValue[0], qtTotalSize ); + } else { + moovMgr->AddChildBox ( udtaRef, qtItem->id, &fullValue[0], qtTotalSize ); + } + + } -#endif + } + +} // TradQT_Manager::UpdateChangedBoxes + +// ================================================================================================= diff --git a/source/XMPFiles/FormatSupport/QuickTime_Support.hpp b/source/XMPFiles/FormatSupport/QuickTime_Support.hpp index 24f903d..160dfc8 100644 --- a/source/XMPFiles/FormatSupport/QuickTime_Support.hpp +++ b/source/XMPFiles/FormatSupport/QuickTime_Support.hpp @@ -3,27 +3,101 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2009 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= -#include "XMP_Environment.h" // ! This must be the first include. -#if ! ( XMP_64 || XMP_UNIXBuild) // Closes at very bottom. +#include "XMP_Environment.h" // ! This must be the first include. -namespace QuickTime_Support -{ - extern bool sMainInitOK; +#include <string> +#include <vector> +#include <map> + +#include "XMPFiles_Impl.hpp" +#include "LargeFileAccess.hpp" +#include "ISOBaseMedia_Support.hpp" +#include "MOOV_Support.hpp" + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// TradQT_Manager +// ============== + +// Support for selected traditional QuickTime metadata items. The supported items are the children +// of the 'moov'/'udta' box whose type begins with 0xA9, a MacRoman copyright symbol. Each of these +// is a box whose contents are a sequence of "mini boxes" analogous to XMP AltText arrays. Each mini +// box has a 16-bit size, 16-bit language code, and text. The language code values are the old +// Macintosh Script Manager langXyz codes, the text encoding is implicit, see Mac Script.h. + +enum { // List of recognized items from the QuickTime 'moov'/'udta' box. + // These items are defined by Adobe. + kQTilst_Reel = 0xA952454CUL, // '©REL' + kQTilst_Timecode = 0xA954494DUL, // '©TIM' + kQTilst_TimeScale = 0xA9545343UL, // '©TSC' + kQTilst_TimeSize = 0xA954535AUL // '©TSZ' +}; + +enum { + kNoMacLang = 0xFFFF, + kNoMacScript = 0xFFFF +}; + +extern bool ConvertToMacLang ( const std::string & utf8Value, XMP_Uns16 macLang, std::string * macValue ); +extern bool ConvertFromMacLang ( const std::string & macValue, XMP_Uns16 macLang, std::string * utf8Value ); + +class TradQT_Manager { +public: + + TradQT_Manager() : changed(false) {}; + + bool ParseCachedBoxes ( const MOOV_Manager & moovMgr ); + + bool ImportSimpleXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr prop ) const; + bool ImportLangAltXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const; + + void ExportSimpleXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr prop, + bool createWithZeroLang = false ); + void ExportLangAltXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr langArray ); + + bool IsChanged() const { return this->changed; }; - bool MainInitialize ( bool ignoreInit ); // For the main thread. - void MainTerminate ( bool ignoreInit ); + void UpdateChangedBoxes ( MOOV_Manager * moovMgr ); + +private: + + struct ValueInfo { + bool marked; + XMP_Uns16 macLang; + XMP_StringPtr xmpLang; // ! Only set if macLang is known, i.e. the value can be converted. + std::string macValue; + ValueInfo() : marked(false), macLang(kNoMacLang), xmpLang("") {}; + }; + typedef std::vector<ValueInfo> ValueVector; + typedef ValueVector::iterator ValueInfoPos; + typedef ValueVector::const_iterator ValueInfoCPos; + + struct ParsedBoxInfo { + XMP_Uns32 id; + ValueVector values; + bool changed; + ParsedBoxInfo() : id(0), changed(false) {}; + ParsedBoxInfo ( XMP_Uns32 _id ) : id(_id), changed(false) {}; + }; + + typedef std::map < XMP_Uns32, ParsedBoxInfo > InfoMap; // Metadata item kind and content info. + typedef InfoMap::iterator InfoMapPos; + typedef InfoMap::const_iterator InfoMapCPos; - bool ThreadInitialize(); // For background threads. - void ThreadTerminate(); + InfoMap parsedBoxes; + bool changed; + + bool ImportLangItem ( const ValueInfo & qtItem, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const; -} // namespace QuickTime_Support +}; // TradQT_Manager -#endif // XMP_64 || XMP_UNIXBuild #endif // __QuickTime_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/RIFF.cpp b/source/XMPFiles/FormatSupport/RIFF.cpp new file mode 100644 index 0000000..3992edd --- /dev/null +++ b/source/XMPFiles/FormatSupport/RIFF.cpp @@ -0,0 +1,879 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +// must have access to handler class fields... +#include "RIFF.hpp" +#include "RIFF_Support.hpp" +#include "RIFF_Handler.hpp" + +using namespace RIFF; + +namespace RIFF { + +// GENERAL STATIC FUNCTIONS //////////////////////////////////////// + +Chunk* getChunk ( ContainerChunk* parent, RIFF_MetaHandler* handler ) +{ + LFA_FileRef file = handler->parent->fileRef; + XMP_Uns8 level = handler->level; + XMP_Uns32 peek = LFA_PeekUns32_LE( file ); + + if ( level == 0 ) + { + XMP_Validate( peek == kChunk_RIFF, "expected RIFF chunk not found", kXMPErr_BadFileFormat ); + XMP_Enforce( parent == NULL ); + } + else + { + XMP_Validate( peek != kChunk_RIFF, "unexpected RIFF chunk below top-level", kXMPErr_BadFileFormat ); + XMP_Enforce( parent != NULL ); + } + + switch( peek ) + { + case kChunk_RIFF: + return new ContainerChunk( parent, handler ); + case kChunk_LIST: + { + if ( level != 1 ) break; // only care on this level + + // look further (beyond 4+4 = beyond id+size) to check on relevance + LFA_Seek( file, 8, SEEK_CUR ); + XMP_Uns32 containerType = LFA_PeekUns32_LE( file ); + LFA_Seek( file, -8, SEEK_CUR ); + + bool isRelevantList = ( containerType== kType_INFO || containerType == kType_Tdat ); + if ( !isRelevantList ) break; + + return new ContainerChunk( parent, handler ); + } + case kChunk_XMP: + if ( level != 1 ) break; // ignore on inappropriate levels (might be compound metadata?) + return new XMPChunk( parent, handler ); + case kChunk_DISP: + { + if ( level != 1 ) break; // only care on this level + // peek even further to see if type is 0x001 and size is reasonable + LFA_Seek( file , 4, SEEK_CUR ); // jump DISP + XMP_Uns32 dispSize = LFA_ReadUns32_LE( file ); + XMP_Uns32 dispType = LFA_ReadUns32_LE( file ); + LFA_Seek( file , -12, SEEK_CUR); // rewind, be in front of chunkID again + + // only take as a relevant disp if both criteria met, + // otherwise treat as generic chunk! + if ( (dispType == 0x0001) && ( dispSize < 256 * 1024 ) ) + { + ValueChunk* r = new ValueChunk( parent, handler ); + handler->dispChunk = r; + return r; + } + break; // treat as irrelevant (non-0x1) DISP chunks as generic chunk + } + case kChunk_bext: + { + if ( level != 1 ) break; // only care on this level + // store for now in a value chunk + ValueChunk* r = new ValueChunk( parent, handler ); + handler->bextChunk = r; + return r; + } + case kChunk_PrmL: + { + if ( level != 1 ) break; // only care on this level + ValueChunk* r = new ValueChunk( parent, handler ); + handler->prmlChunk = r; + return r; + } + case kChunk_Cr8r: + { + if ( level != 1 ) break; // only care on this level + ValueChunk* r = new ValueChunk( parent, handler ); + handler->cr8rChunk = r; + return r; + } + case kChunk_JUNQ: + case kChunk_JUNK: + { + JunkChunk* r = new JunkChunk( parent, handler ); + return r; + } + } + // this "default:" section must be ouside switch bracket, to be + // reachable by all those break statements above: + + + // digest 'valuable' container chunks: LIST:INFO, LIST:Tdat + bool insideRelevantList = ( level==2 && parent->id == kChunk_LIST + && ( parent->containerType== kType_INFO || parent->containerType == kType_Tdat )); + + if ( insideRelevantList ) + { + ValueChunk* r = new ValueChunk( parent, handler ); + return r; + } + + // general chunk of no interest, treat as unknown blob + return new Chunk( parent, handler, true, chunk_GENERAL ); +} + +// BASE CLASS CHUNK /////////////////////////////////////////////// +// ad hoc creation +Chunk::Chunk( ContainerChunk* parent, ChunkType c, XMP_Uns32 id ) +{ + this->chunkType = c; // base class assumption + this->parent = parent; + this->id = id; + this->oldSize = 0; + this->newSize = 8; + this->oldPos = 0; // inevitable for ad-hoc + this->needSizeFix = false; + + // good parenting for latter destruction + if ( this->parent != NULL ) + { + this->parent->children.push_back( this ); + if( this->chunkType == chunk_VALUE ) + this->parent->childmap.insert( make_pair( this->id, (ValueChunk*) this ) ); + } +} + +// parsing creation +Chunk::Chunk( ContainerChunk* parent, RIFF_MetaHandler* handler, bool skip, ChunkType c ) +{ + chunkType = c; // base class assumption + this->parent = parent; + this->oldSize = 0; + this->hasChange = false; // [2414649] valid assumption at creation time + + LFA_FileRef file = handler->parent->fileRef; + + this->oldPos = LFA_Tell( file ); + this->id = LFA_ReadUns32_LE( file ); + this->oldSize = LFA_ReadUns32_LE( file ) + 8; + + // Make sure the size is within expected bounds. + XMP_Int64 chunkEnd = this->oldPos + this->oldSize; + XMP_Int64 chunkLimit = handler->oldFileSize; + if ( parent != 0 ) chunkLimit = parent->oldPos + parent->oldSize; + if ( chunkEnd > chunkLimit ) { + bool isUpdate = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenForUpdate ); + bool repairFile = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenRepairFile ); + if ( (! isUpdate) || (repairFile && (parent == 0)) ) { + this->oldSize = chunkLimit - this->oldPos; + } else { + XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat ); + } + } + + this->newSize = this->oldSize; + this->needSizeFix = false; + + if ( skip ) + { + bool ok; + LFA_Seek( file, this->oldSize - 8 , SEEK_CUR, &ok ); + XMP_Validate( ok , "skipped beyond end of file (truncated file?)", kXMPErr_BadFileFormat ); + } + + // "good parenting", essential for latter destruction. + if ( this->parent != NULL ) + { + this->parent->children.push_back( this ); + if( this->chunkType == chunk_VALUE ) + this->parent->childmap.insert( make_pair( this->id, (ValueChunk*) this ) ); + } +} + +void Chunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + // only unknown chunks should reach this method, + // all others must reach overloads, hence little to do here: + hasChange = false; // unknown chunk ==> no change, naturally + this->newSize = this->oldSize; +} + +std::string Chunk::toString(XMP_Uns8 level ) +{ + char buffer[256]; + snprintf( buffer, 255, "%.4s -- " + "oldSize: 0x%.8llX, " + "newSize: 0x%.8llX, " + "oldPos: 0x%.8llX\n", + (char*)(&this->id), this->oldSize, this->newSize, this->oldPos ); + return std::string(buffer); +} + +void Chunk::write( RIFF_MetaHandler* handler, LFA_FileRef file , bool isMainChunk ) +{ + throw new XMP_Error(kXMPErr_InternalFailure, "Chunk::write never to be called for unknown chunks."); +} + +Chunk::~Chunk() +{ + //nothing +} + +// CONTAINER CHUNK ///////////////////////////////////////////////// +// a) creation +// [2376832] expectedSize - minimum padding "parking size" to use, if not available append to end +ContainerChunk::ContainerChunk( ContainerChunk* parent, XMP_Uns32 id, XMP_Uns32 containerType ) : Chunk( NULL /* !! */, chunk_CONTAINER, id ) +{ + // accept no unparented ConatinerChunks + XMP_Enforce( parent != NULL ); + + this->containerType = containerType; + this->newSize = 12; + this->parent = parent; + + chunkVect* siblings = &parent->children; + + // add at end. ( oldSize==0 will flag optimization later in the process) + siblings->push_back( this ); +} + +// b) parsing +ContainerChunk::ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_CONTAINER ) +{ + bool repairMode = ( 0 != ( handler->parent->openFlags & kXMPFiles_OpenRepairFile )); + + try + { + LFA_FileRef file = handler->parent->fileRef; + XMP_Uns8 level = handler->level; + + // get type of container chunk + this->containerType = LFA_ReadUns32_LE( file ); + + // ensure legality of top-level chunks + if ( level == 0 && handler->riffChunks.size() > 0 ) + { + XMP_Validate( handler->parent->format == kXMP_AVIFile, "only AVI may have multiple top-level chunks", kXMPErr_BadFileFormat ); + XMP_Validate( this->containerType == kType_AVIX, "all chunks beyond main chunk must be type AVIX", kXMPErr_BadFileFormat ); + } + + // has *relevant* subChunks? (there might be e.g. non-INFO LIST chunks we don't care about) + bool hasSubChunks = ( ( this->id == kChunk_RIFF ) || + ( this->id == kChunk_LIST && this->containerType == kType_INFO ) || + ( this->id == kChunk_LIST && this->containerType == kType_Tdat ) + ); + XMP_Int64 endOfChunk = this->oldPos + this->oldSize; + + // this statement catches beyond-EoF-offsets on any level + // exception: level 0, tolerate if in repairMode + if ( (level == 0) && repairMode && (endOfChunk > handler->oldFileSize) ) + { + endOfChunk = handler->oldFileSize; // assign actual file size + this->oldSize = endOfChunk - this->oldPos; //reversely calculate correct oldSize + } + + XMP_Validate( endOfChunk <= handler->oldFileSize, "offset beyond EoF", kXMPErr_BadFileFormat ); + + Chunk* curChild = 0; + if ( hasSubChunks ) + { + handler->level++; + while ( LFA_Tell( file ) < endOfChunk ) + { + curChild = RIFF::getChunk( this, handler ); + + // digest pad byte - no value validation (0), since some 3rd party files have non-0-padding. + if ( LFA_Tell(file) % 2 == 1 ) + { + // [1521093] tolerate missing pad byte at very end of file: + XMP_Uns8 pad; + LFA_Read ( file, &pad, 1 ); // Read the pad, tolerate being at EOF. + + } + + // within relevant LISTs, relentlesly delete junk chunks (create a single one + // at end as part of updateAndChanges() + if ( (containerType== kType_INFO || containerType == kType_Tdat) + && ( curChild->chunkType == chunk_JUNK ) ) + { + this->children.pop_back(); + delete curChild; + } // for other chunks: join neighouring Junk chunks into one + else if ( (curChild->chunkType == chunk_JUNK) && ( this->children.size() >= 2 ) ) + { + // nb: if there are e.g 2 chunks, then last one is at(1), prev one at(0) ==> '-2' + Chunk* prevChunk = this->children.at( this->children.size() - 2 ); + if ( prevChunk->chunkType == chunk_JUNK ) + { + // stack up size to prior chunk + prevChunk->oldSize += curChild->oldSize; + prevChunk->newSize += curChild->newSize; + XMP_Enforce( prevChunk->oldSize == prevChunk->newSize ); + // destroy current chunk + this->children.pop_back(); + delete curChild; + } + } + } + handler->level--; + XMP_Validate( LFA_Tell( file ) == endOfChunk, "subchunks exceed outer chunk size", kXMPErr_BadFileFormat ); + + // pointers for later legacy processing + if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_INFO ) + handler->listInfoChunk = this; + if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_Tdat ) + handler->listTdatChunk = this; + } + else // skip non-interest container chunk + { + bool ok; + LFA_Seek( file, this->oldSize - 8 - 4, SEEK_CUR, &ok ); + XMP_Validate( ok , "skipped beyond end of file 2 (truncated file?)", kXMPErr_BadFileFormat ); + } // if - else + + } // try + catch (XMP_Error& e) { + this->release(); // free resources + if ( this->parent != 0) + this->parent->children.pop_back(); // hereby taken care of, so removing myself... + + throw e; // re-throw + } +} + +void ContainerChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + + // Walk the container subtree adjusting the children that have size changes. The only containers + // are RIFF and LIST chunks, they are treated differently. + // + // LISTs get recomposed as a whole. Existing JUNK children of a LIST are removed, existing real + // children are left in order with their new size, new children have already been appended. The + // LIST as a whole gets a new size that is the sum of the final children. + // + // Special rules apply to various children of a RIFF container. FIrst, adjacent JUNK children + // are combined, this simplifies maximal reuse. The children are recursively adjusted in order + // to get their final size. + // + // Try to determine the final placement of each RIFF child using general rules: + // - if the size is unchanged: leave at current location + // - if the chunk is at the end of the last RIFF chunk and grows: leave at current location + // - if there is enough following JUNK: add part of the JUNK, adjust remaining JUNK size + // - if it shrinks by 9 bytes or more: carve off trailing JUNK + // - try to find adequate JUNK in the current parent + // + // Use child-specific rules as a last resort: + // - if it is LIST:INFO: delete it, must be in first RIFF chunk + // - for others: move to end of last RIFF chunk, make old space JUNK + + // ! Don't create any junk chunks of exactly 8 bytes, just a header and no content. That has a + // ! size field of zero, which hits a crashing bug in some versions of Windows Media Player. + + bool isRIFFContainer = (this->id == kChunk_RIFF); + bool isLISTContainer = (this->id == kChunk_LIST); + XMP_Enforce ( isRIFFContainer | isLISTContainer ); + + XMP_Index childIndex; // Could be local to the loops, this simplifies debuging. Need a signed type! + Chunk * currChild; + + if ( this->children.empty() ) { + if ( isRIFFContainer) { + this->newSize = 12; // Keep a minimal size container. + } else { + this->newSize = 0; // Will get removed from parent in outer call. + } + this->hasChange = true; + return; // Nothing more to do without children. + } + + // Collapse adjacent RIFF junk children, remove all LIST junk children. Work back to front to + // simplify the effect of .erase() on the loop. Purposely ignore the first chunk. + + for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex > 0; --childIndex ) { + + currChild = this->children[childIndex]; + if ( currChild->chunkType != chunk_JUNK ) continue; + + if ( isRIFFContainer ) { + Chunk * prevChild = this->children[childIndex-1]; + if ( prevChild->chunkType != chunk_JUNK ) continue; + prevChild->oldSize += currChild->oldSize; + prevChild->newSize += currChild->newSize; + prevChild->hasChange = true; + } + + this->children.erase ( this->children.begin() + childIndex ); + delete currChild; + this->hasChange = true; + + } + + // Process the children of RIFF and LIST containers to get their final size. Remove empty + // children. Work back to front to simplify the effect of .erase() on the loop. Do not ignore + // the first chunk. + + for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex >= 0; --childIndex ) { + + currChild = this->children[childIndex]; + + ++handler->level; + currChild->changesAndSize ( handler ); + --handler->level; + + if ( (currChild->newSize == 8) || (currChild->newSize == 0) ) { // ! The newSIze is supposed to include the header. + this->children.erase ( this->children.begin() + childIndex ); + delete currChild; + this->hasChange = true; + } else { + this->hasChange |= currChild->hasChange; + currChild->needSizeFix = (currChild->newSize != currChild->oldSize); + if ( currChild->needSizeFix && (currChild->newSize > currChild->oldSize) && + (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) { + // Let an existing last-in-file chunk grow in-place. Shrinking is conceptually OK, + // but complicates later sanity check that the main AVI chunk is not OK to append + // other chunks later. Ignore new chunks, they might reuse junk space. + if ( currChild->oldSize != 0 ) currChild->needSizeFix = false; + } + } + + } + + // Go through the children of a RIFF container, adjusting the placement as necessary. In brief, + // things can only grow at the end of the last RIFF chunk, and non-junk chunks can't be shifted. + + if ( isRIFFContainer ) { + + for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) { + + currChild = this->children[childIndex]; + if ( ! currChild->needSizeFix ) continue; + currChild->needSizeFix = false; + + XMP_Int64 sizeDiff = currChild->newSize - currChild->oldSize; // Positive for growth. + XMP_Uns8 padSize = (currChild->newSize & 1); // Need a pad for odd size. + + // See if the following chunk is junk that can be utilized. + + Chunk * nextChild = 0; + if ( childIndex+1 < (XMP_Index)this->children.size() ) nextChild = this->children[childIndex+1]; + + if ( (nextChild != 0) && (nextChild->chunkType == chunk_JUNK) ) { + if ( nextChild->newSize >= (9 + sizeDiff + padSize) ) { + + // Incorporate part of the trailing junk, or make the trailing junk grow. + nextChild->newSize -= sizeDiff; + nextChild->newSize -= padSize; + nextChild->hasChange = true; + continue; + + } else if ( nextChild->newSize == (sizeDiff + padSize) ) { + + // Incorporate all of the trailing junk. + this->children.erase ( this->children.begin() + childIndex + 1 ); + delete nextChild; + continue; + + } + } + + // See if the chunk shrinks enough to turn the leftover space into junk. + + if ( (sizeDiff + padSize) <= -9 ) { + this->children.insert ( (this->children.begin() + childIndex + 1), new JunkChunk ( NULL, ((-sizeDiff) - padSize) ) ); + continue; + } + + // Look through the parent for a usable span of junk. + + XMP_Index junkIndex; + Chunk * junkChunk = 0; + for ( junkIndex = 0; junkIndex < (XMP_Index)this->children.size(); ++junkIndex ) { + junkChunk = this->children[junkIndex]; + if ( junkChunk->chunkType != chunk_JUNK ) continue; + if ( (junkChunk->newSize >= (9 + currChild->newSize + padSize)) || + (junkChunk->newSize == (currChild->newSize + padSize)) ) break; + } + + if ( junkIndex < (XMP_Index)this->children.size() ) { + + // Use part or all of the junk for the relocated chunk, replace the old space with junk. + + if ( junkChunk->newSize == (currChild->newSize + padSize) ) { + + // The found junk is an exact fit. + this->children[junkIndex] = currChild; + delete junkChunk; + + } else { + + // The found junk has excess space. Insert the moving chunk and shrink the junk. + XMP_Assert ( junkChunk->newSize >= (9 + currChild->newSize + padSize) ); + junkChunk->newSize -= (currChild->newSize + padSize); + junkChunk->hasChange = true; + this->children.insert ( (this->children.begin() + junkIndex), currChild ); + if ( junkIndex < childIndex ) ++childIndex; // The insertion moved the current child. + + } + + if ( currChild->oldSize != 0 ) { + this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize ); // Replace the old space with junk. + } else { + this->children.erase ( this->children.begin() + childIndex ); // Remove the newly created chunk's old location. + --childIndex; // Make the next loop iteration not skip a chunk. + } + + continue; + + } + + // If this is a LIST:INFO chunk not in the last of multiple RIFF chunks, then give up + // and replace it with oldSize junk. Preserve the first RIFF chunk's original size. + + bool isListInfo = (currChild->id == kChunk_LIST) && (currChild->chunkType == chunk_CONTAINER) && + (((ContainerChunk*)currChild)->containerType == kType_INFO); + + if ( isListInfo && (handler->riffChunks.size() > 1) && + (this->id == kChunk_RIFF) && (this != handler->lastChunk) ) { + + if ( currChild->oldSize != 0 ) { + this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize ); + } else { + this->children.erase ( this->children.begin() + childIndex ); + --childIndex; // Make the next loop iteration not skip a chunk. + } + + delete currChild; + continue; + + } + + // Move the chunk to the end of the last RIFF chunk and make the old space junk. + + if ( (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) continue; // Already last. + + handler->lastChunk->children.push_back( currChild ); + if ( currChild->oldSize != 0 ) { + this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize ); // Replace the old space with junk. + } else { + this->children.erase ( this->children.begin() + childIndex ); // Remove the newly created chunk's old location. + --childIndex; // Make the next loop iteration not skip a chunk. + } + + } + + } + + // Compute the finished container's new size (for both RIFF and LIST). + + this->newSize = 12; // Start with standard container header. + for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) { + currChild = this->children[childIndex]; + this->newSize += currChild->newSize; + this->newSize += (this->newSize & 1); // Round up if odd. + } + + XMP_Validate ( (this->newSize <= 0xFFFFFFFFLL), "No single chunk may be above 4 GB", kXMPErr_Unimplemented ); + +} + +std::string ContainerChunk::toString(XMP_Uns8 level ) +{ + XMP_Int64 offset= 12; // compute offsets, just for informational purposes + // (actually only correct for first chunk) + + char buffer[256]; + snprintf( buffer, 255, "%.4s:%.4s, " + "oldSize: 0x%8llX, " + "newSize: 0x%.8llX, " + "oldPos: 0x%.8llX\n", + (char*)(&this->id), (char*)(&this->containerType), this->oldSize, this->newSize, this->oldPos ); + + std::string r(buffer); + chunkVectIter iter; + for( iter = this->children.begin(); iter != this->children.end(); iter++ ) + { + char buffer[256]; + snprintf( buffer, 250, "offset 0x%.8llX", offset ); + r += std::string ( level*4, ' ' ) + std::string( buffer ) + ":" + (*iter)->toString( level + 1 ); + offset += (*iter)->newSize; + if ( offset % 2 == 1 ) + offset++; + } + return std::string(r); +} + +void ContainerChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk ) +{ + bool ok; + if ( isMainChunk ) + LFA_Rewind( file ); + + // enforce even position + XMP_Int64 chunkStart = LFA_Tell(file); + XMP_Int64 chunkEnd = chunkStart + this->newSize; + XMP_Enforce( chunkStart % 2 == 0 ); + chunkVect *rc = &this->children; + + // [2473303] have to write back-to-front to avoid stomp-on-feet + XMP_Int64 childStart = chunkEnd; + for ( XMP_Int32 chunkNo = (XMP_Int32)(rc->size() -1); chunkNo >= 0; chunkNo-- ) + { + Chunk* cur = rc->at(chunkNo); + + // pad byte first + if ( cur->newSize % 2 == 1 ) + { + childStart--; + LFA_Seek( file, childStart, SEEK_SET ); + LFA_WriteUns8( file, 0 ); + } + + // then contents + childStart-= cur->newSize; + LFA_Seek( file, childStart, SEEK_SET ); + switch ( cur->chunkType ) + { + case chunk_GENERAL: //COULDDO enfore no change, since not write-out-able + if ( cur->oldPos != childStart ) + LFA_Move( file, cur->oldPos, file, childStart, cur->oldSize, 0, 0 ); + break; + default: + cur->write( handler, file, false ); + break; + } // switch + + } // for + XMP_Enforce ( chunkStart + 12 == childStart); + LFA_Seek( file, chunkStart, SEEK_SET, &ok ); + + LFA_WriteUns32_LE( file, this->id ); + LFA_WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above + LFA_WriteUns32_LE( file, this->containerType ); + +} + +void ContainerChunk::release() +{ + // free subchunks + Chunk* curChunk; + while( ! this->children.empty() ) + { + curChunk = this->children.back(); + delete curChunk; + this->children.pop_back(); + } +} + +ContainerChunk::~ContainerChunk() +{ + this->release(); // free resources +} + +// XMP CHUNK /////////////////////////////////////////////// +// a) create + +// a) creation +XMPChunk::XMPChunk( ContainerChunk* parent ) : Chunk( parent, chunk_XMP , kChunk_XMP ) +{ + // nothing +} + +// b) parse +XMPChunk::XMPChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_XMP ) +{ + chunkType = chunk_XMP; + LFA_FileRef file = handler->parent->fileRef; + XMP_Uns8 level = handler->level; + + handler->packetInfo.offset = this->oldPos + 8; + handler->packetInfo.length = (XMP_Int32) this->oldSize - 8; + + handler->xmpPacket.reserve ( handler->packetInfo.length ); + handler->xmpPacket.assign ( handler->packetInfo.length, ' ' ); + LFA_Read ( file, (void*)handler->xmpPacket.data(), handler->packetInfo.length, kLFA_RequireAll ); + + handler->containsXMP = true; // last, after all possible failure + + // pointer for later processing + handler->xmpChunk = this; +} + +void XMPChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + XMP_Enforce( &handler->xmpPacket != 0 ); + XMP_Enforce( handler->xmpPacket.size() > 0 ); + this->newSize = 8 + handler->xmpPacket.size(); + + XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure ); + + // a complete no-change would have been caught in XMPFiles common code anyway + this->hasChange = true; +} + +void XMPChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk ) +{ + LFA_WriteUns32_LE( file, kChunk_XMP ); + LFA_WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above + LFA_Write( file, handler->xmpPacket.data(), (XMP_Int32)handler->xmpPacket.size() ); +} + +// Value CHUNK /////////////////////////////////////////////// +// a) creation +ValueChunk::ValueChunk( ContainerChunk* parent, std::string value, XMP_Uns32 id ) : Chunk( parent, chunk_VALUE, id ) +{ + this->oldValue = std::string(); + this->SetValue( value ); +} + +// b) parsing +ValueChunk::ValueChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_VALUE ) +{ + // set value: ----------------- + LFA_FileRef file = handler->parent->fileRef; + XMP_Uns8 level = handler->level; + + // unless changed through reconciliation, assume for now. + // IMPORTANT to stay true to the original (no \0 cleanup or similar) + // since unknown value chunks might not be fully understood, + // hence must be precisely preserved !!! + + XMP_Int32 length = (XMP_Int32) this->oldSize - 8; + this->oldValue.reserve( length ); + this->oldValue.assign( length + 1, '\0' ); + LFA_Read ( file, (void*)this->oldValue.data(), length, kLFA_RequireAll ); + + this->newValue = this->oldValue; + this->newSize = this->oldSize; +} + +void ValueChunk::SetValue( std::string value, bool optionalNUL /* = false */ ) +{ + this->newValue.assign( value ); + if ( (! optionalNUL) || ((value.size() & 1) == 1) ) { + // ! The NUL should be optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte. + this->newValue.append( 1, '\0' ); // append zero termination as explicit part of string + } + this->newSize = this->newValue.size() + 8; +} + +void ValueChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + // Don't simply assign to this->hasChange, it might already be true. + if ( this->newValue.size() != this->oldValue.size() ) { + this->hasChange = true; + } else if ( strncmp ( this->oldValue.c_str(), this->newValue.c_str(), this->newValue.size() ) != 0 ) { + this->hasChange = true; + } +} + +void ValueChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk ) +{ + LFA_WriteUns32_LE( file, this->id ); + LFA_WriteUns32_LE( file, (XMP_Uns32)this->newSize - 8 ); + LFA_Write( file, this->newValue.data() , (XMP_Int32)this->newSize - 8 ); +} + +/* remove value chunk if existing. + return true if it was existing. */ +bool ContainerChunk::removeValue( XMP_Uns32 id ) +{ + valueMap* cm = &this->childmap; + valueMapIter iter = cm->find( id ); + + if( iter == cm->end() ) + return false; //not found + + ValueChunk* propChunk = iter->second; + + // remove from vector (difficult) + chunkVect* cv = &this->children; + chunkVectIter cvIter; + for (cvIter = cv->begin(); cvIter != cv->end(); ++cvIter ) + { + if ( (*cvIter)->id == id ) + break; // found! + } + XMP_Validate( cvIter != cv->end(), "property not found in children vector", kXMPErr_InternalFailure ); + cv->erase( cvIter ); + + // remove from map (easy) + cm->erase( iter ); + + delete propChunk; + return true; // found and removed +} + +/* returns iterator to (first) occurence of this chunk. + iterator to the end of the map if chunk pointer is not found */ +chunkVectIter ContainerChunk::getChild( Chunk* needle ) +{ + chunkVectIter iter; + for( iter = this->children.begin(); iter != this->children.end(); iter++ ) + { + Chunk* temp1 = *iter; + Chunk* temp2 = needle; + if ( (*iter) == needle ) return iter; + } + return this->children.end(); +} + +/* replaces a chunk by a JUNK chunk. + Also frees memory of prior chunk. */ +void ContainerChunk::replaceChildWithJunk( Chunk* child, bool deleteChild ) +{ + chunkVectIter iter = getChild( child ); + if ( iter == this->children.end() ) { + throw new XMP_Error(kXMPErr_InternalFailure, "replaceChildWithJunk: childChunk not found."); + } + + *iter = new JunkChunk ( NULL, child->oldSize ); + if ( deleteChild ) delete child; + + this->hasChange = true; +} + +// JunkChunk /////////////////////////////////////////////////// +// a) creation +JunkChunk::JunkChunk( ContainerChunk* parent, XMP_Int64 size ) : Chunk( parent, chunk_JUNK, kChunk_JUNK ) +{ + XMP_Assert( size >= 8 ); + this->oldSize = size; + this->newSize = size; + this->hasChange = true; +} + +// b) parsing +JunkChunk::JunkChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, true, chunk_JUNK ) +{ + chunkType = chunk_JUNK; +} + +void JunkChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + this->newSize = this->oldSize; // optimization at a later stage + XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure ); + if ( this->id == kChunk_JUNQ ) this->hasChange = true; // Force ID change to JUNK. +} + +// zeroBuffer, etc to write out empty native padding +const static XMP_Uns32 kZeroBufferSize64K = 64 * 1024; +static XMP_Uns8 kZeroes64K [ kZeroBufferSize64K ]; // C semantics guarantee zero initialization. + +void JunkChunk::write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk ) +{ + LFA_WriteUns32_LE( file, kChunk_JUNK ); // write JUNK, never JUNQ + XMP_Enforce( this->newSize < 0xFFFFFFFF ); + XMP_Enforce( this->newSize >= 8 ); // minimum size of any chunk + XMP_Uns32 innerSize = (XMP_Uns32)this->newSize - 8; + LFA_WriteUns32_LE( file, innerSize ); + + // write out in 64K chunks + while ( innerSize > kZeroBufferSize64K ) + { + LFA_Write( file, kZeroes64K , kZeroBufferSize64K ); + innerSize -= kZeroBufferSize64K; + } + LFA_Write( file, kZeroes64K , innerSize ); +} + +} // namespace RIFF diff --git a/source/XMPFiles/FormatSupport/RIFF.hpp b/source/XMPFiles/FormatSupport/RIFF.hpp new file mode 100644 index 0000000..34b2100 --- /dev/null +++ b/source/XMPFiles/FormatSupport/RIFF.hpp @@ -0,0 +1,316 @@ +#ifndef __RIFF_hpp__ +#define __RIFF_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMP_Environment.h" // ! This must be the first include. +#include <vector> +#include <map> +#include "XMPFiles_Impl.hpp" + +// ahead declaration: +class RIFF_MetaHandler; + +namespace RIFF { + + enum ChunkType { + chunk_GENERAL, //unknown or not relevant + chunk_CONTAINER, + chunk_XMP, + chunk_VALUE, + chunk_JUNK, + NO_CHUNK // used as precessor to first chunk, etc. + }; + + // ahead declarations + class Chunk; + class ContainerChunk; + class ValueChunk; + class XMPChunk; + + // (scope: only used in RIFF_Support and RIFF_Handler.cpp + // ==> no need to overspecify with lengthy names ) + + typedef std::vector<Chunk*> chunkVect; // coulddo: narrow down toValueChunk (could give trouble with JUNK though) + typedef chunkVect::iterator chunkVectIter; // or refactor ?? + + typedef std::vector<ContainerChunk*> containerVect; + typedef containerVect::iterator containerVectIter; + + typedef std::map<XMP_Uns32,ValueChunk*> valueMap; + typedef valueMap::iterator valueMapIter; + + + // format chunks+types + const XMP_Uns32 kChunk_RIFF = 0x46464952; + const XMP_Uns32 kType_AVI_ = 0x20495641; + const XMP_Uns32 kType_AVIX = 0x58495641; + const XMP_Uns32 kType_WAVE = 0x45564157; + + const XMP_Uns32 kChunk_JUNK = 0x4B4E554A; + const XMP_Uns32 kChunk_JUNQ = 0x514E554A; + + // other container chunks + const XMP_Uns32 kChunk_LIST = 0x5453494C; + const XMP_Uns32 kType_INFO = 0x4F464E49; + const XMP_Uns32 kType_Tdat = 0x74616454; + + // other relevant chunks + const XMP_Uns32 kChunk_XMP = 0x584D505F; // "_PMX" + + // relevant for Index Correction + // LIST: + const XMP_Uns32 kType_hdrl = 0x6C726468; + const XMP_Uns32 kType_strl = 0x6C727473; + const XMP_Uns32 kChunk_indx = 0x78646E69; + const XMP_Uns32 kChunk_ixXX = 0x58587869; + const XMP_Uns32 kType_movi = 0x69766F6D; + + //should occur only in AVI + const XMP_Uns32 kChunk_Cr8r = 0x72387243; + const XMP_Uns32 kChunk_PrmL = 0x4C6D7250; + + //should occur only in WAV + const XMP_Uns32 kChunk_DISP = 0x50534944; + const XMP_Uns32 kChunk_bext = 0x74786562; + + // LIST/INFO constants + const XMP_Uns32 kPropChunkIART = 0x54524149; + const XMP_Uns32 kPropChunkICMT = 0x544D4349; + const XMP_Uns32 kPropChunkICOP = 0x504F4349; + const XMP_Uns32 kPropChunkICRD = 0x44524349; + const XMP_Uns32 kPropChunkIENG = 0x474E4549; + const XMP_Uns32 kPropChunkIGNR = 0x524E4749; + const XMP_Uns32 kPropChunkINAM = 0x4D414E49; + const XMP_Uns32 kPropChunkISFT = 0x54465349; + const XMP_Uns32 kPropChunkIARL = 0x4C524149; + + const XMP_Uns32 kPropChunkIMED = 0x44454D49; + const XMP_Uns32 kPropChunkISRF = 0x46525349; + const XMP_Uns32 kPropChunkICMS = 0x4C524149; + const XMP_Uns32 kPropChunkIPRD = 0x534D4349; + const XMP_Uns32 kPropChunkISRC = 0x44525049; + const XMP_Uns32 kPropChunkITCH = 0x43525349; + + const XMP_Uns32 kPropChunk_tc_O =0x4F5F6374; + const XMP_Uns32 kPropChunk_tc_A =0x415F6374; + const XMP_Uns32 kPropChunk_rn_O =0x4F5F6E72; + const XMP_Uns32 kPropChunk_rn_A =0x415F6E72; + + /////////////////////////////////////////////////////////////// + + enum PropType { // from a simplified, opinionated legacy angle + prop_SIMPLE, + prop_TIMEVALUE, + prop_LOCALIZED_TEXT, + prop_ARRAYITEM, // ( here: a solitary one) + }; + + struct Mapping { + XMP_Uns32 chunkID; + const char* ns; + const char* prop; + PropType propType; + }; + + // bext Mappings, piece-by-piece: + static Mapping bextDescription = { 0, kXMP_NS_BWF, "description", prop_SIMPLE }; + static Mapping bextOriginator = { 0, kXMP_NS_BWF, "originator", prop_SIMPLE }; + static Mapping bextOriginatorRef = { 0, kXMP_NS_BWF, "originatorReference", prop_SIMPLE }; + static Mapping bextOriginationDate = { 0, kXMP_NS_BWF, "originationDate", prop_SIMPLE }; + static Mapping bextOriginationTime = { 0, kXMP_NS_BWF, "originationTime", prop_SIMPLE }; + static Mapping bextTimeReference = { 0, kXMP_NS_BWF, "timeReference", prop_SIMPLE }; + static Mapping bextVersion = { 0, kXMP_NS_BWF, "version", prop_SIMPLE }; + static Mapping bextUMID = { 0, kXMP_NS_BWF, "umid", prop_SIMPLE }; + static Mapping bextCodingHistory = { 0, kXMP_NS_BWF, "codingHistory", prop_SIMPLE }; + + // LIST:INFO properties + static Mapping listInfoProps[] = { + // reconciliations CS4 and before: + { kPropChunkIART, kXMP_NS_DM, "artist" , prop_SIMPLE }, + { kPropChunkICMT, kXMP_NS_DM, "logComment" , prop_SIMPLE }, + { kPropChunkICOP, kXMP_NS_DC, "rights" , prop_LOCALIZED_TEXT }, + { kPropChunkICRD, kXMP_NS_XMP, "CreateDate" , prop_SIMPLE }, + { kPropChunkIENG, kXMP_NS_DM, "engineer" , prop_SIMPLE }, + { kPropChunkIGNR, kXMP_NS_DM, "genre" , prop_SIMPLE }, + { kPropChunkINAM, kXMP_NS_DC, "title" , prop_LOCALIZED_TEXT }, // ( was wrongly dc:album in pre-CS4) + { kPropChunkISFT, kXMP_NS_XMP, "CreatorTool", prop_SIMPLE }, + + // RIFF/*/LIST/INFO properties, new in CS5, both AVI and WAV + + { kPropChunkIMED, kXMP_NS_DC, "source" , prop_SIMPLE }, + { kPropChunkISRF, kXMP_NS_DC, "type" , prop_ARRAYITEM }, + // TO ENABLE { kPropChunkIARL, kXMP_NS_DC, "subject" , prop_SIMPLE }, // array !! (not x-default language alternative) + //{ kPropChunkICMS, to be decided, "" , prop_SIMPLE }, + //{ kPropChunkIPRD, to be decided, "" , prop_SIMPLE }, + //{ kPropChunkISRC, to be decided, "" , prop_SIMPLE }, + //{ kPropChunkITCH, to be decided, "" , prop_SIMPLE }, + + { 0, 0, 0 } // sentinel + }; + + static Mapping listTdatProps[] = { + // reconciliations CS4 and before: + { kPropChunk_tc_O, kXMP_NS_DM, "startTimecode" , prop_TIMEVALUE }, // special: must end up in dm:timeValue child + { kPropChunk_tc_A, kXMP_NS_DM, "altTimecode" , prop_TIMEVALUE }, // special: must end up in dm:timeValue child + { kPropChunk_rn_O, kXMP_NS_DM, "tapeName" , prop_SIMPLE }, + { kPropChunk_rn_A, kXMP_NS_DM, "altTapeName" , prop_SIMPLE }, + { 0, 0, 0 } // sentinel + }; + + // ================================================================================================= + // ImportCr8rItems + // =============== + #pragma pack ( push, 1 ) + struct PrmLBoxContent { + XMP_Uns32 magic; + XMP_Uns32 size; + XMP_Uns16 verAPI; + XMP_Uns16 verCode; + XMP_Uns32 exportType; + XMP_Uns16 MacVRefNum; + XMP_Uns32 MacParID; + char filePath[260]; + }; + + enum { kExportTypeMovie = 0, kExportTypeStill = 1, kExportTypeAudio = 2, kExportTypeCustom = 3 }; + + struct Cr8rBoxContent { + XMP_Uns32 magic; + XMP_Uns32 size; + XMP_Uns16 majorVer; + XMP_Uns16 minorVer; + XMP_Uns32 creatorCode; + XMP_Uns32 appleEvent; + char fileExt[16]; + char appOptions[16]; + char appName[32]; + }; + #pragma pack ( pop ) + + // static getter, determines appropriate chunkType (peeking)and returns + // the respective constructor. It's the caller's responsibility to + // delete obtained chunk. + Chunk* getChunk ( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + class Chunk + { + public: + ChunkType chunkType; // set by constructor + ContainerChunk* parent; // 0 on top-level + + XMP_Uns32 id; // the first four bytes, first byte of highest value + XMP_Int64 oldSize; // actual chunk size INCLUDING the 8/12 header bytes, + XMP_Int64 oldPos; // file position of this chunk + + // both set as part of changesAndSize() + XMP_Int64 newSize; + bool hasChange; + bool needSizeFix; // used in changesAndSize() only + + // Constructors /////////////////////// + // parsing + Chunk( ContainerChunk* parent, RIFF_MetaHandler* handler, bool skip, ChunkType c /*= chunk_GENERAL*/ ); + // ad-hoc creation + Chunk( ContainerChunk* parent, ChunkType c, XMP_Uns32 id ); + + /* returns true, if something has changed in chunk (which needs specific write-out, + this->newSize is expected to be set by this routine */ + virtual void changesAndSize( RIFF_MetaHandler* handler ); + virtual std::string toString(XMP_Uns8 level = 0); + virtual void write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk = false ); + + virtual ~Chunk(); + + }; // class Chunk + + class XMPChunk : public Chunk + { + public: + XMPChunk( ContainerChunk* parent ); + XMPChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + void changesAndSize( RIFF_MetaHandler* handler ); + void write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk = false ); + + }; + + // any chunk, whose value should be stored, e.g. LIST:INFO, LIST:Tdat + class ValueChunk : public Chunk + { + public: + std::string oldValue, newValue; + + // for ad-hoc creation (upon write) + ValueChunk( ContainerChunk* parent, std::string value, XMP_Uns32 id ); + + // for parsing + ValueChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + enum { kNULisOptional = true }; + + void SetValue( std::string value, bool optionalNUL = false ); + void changesAndSize( RIFF_MetaHandler* handler ); + void write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk = false ); + + // own destructor not needed. + }; + + // relevant (level 1) JUNQ and JUNK chunks... + class JunkChunk : public Chunk + { + public: + // construction + JunkChunk( ContainerChunk* parent, XMP_Int64 size ); + // parsing + JunkChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + // own destructor not needed. + + void changesAndSize( RIFF_MetaHandler* handler ); + void write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk = false ); + }; + + + class ContainerChunk : public Chunk + { + public: + XMP_Uns32 containerType; // e.g. kType_INFO as in "LIST:INFO" + + chunkVect children; // used for cleanup/destruction, ordering... + valueMap childmap; // only for efficient *value* access (inside LIST), *not* used for other containers + + // construct + ContainerChunk( ContainerChunk* parent, XMP_Uns32 id, XMP_Uns32 containerType ); + // parse + ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + bool removeValue( XMP_Uns32 id ); + + /* returns iterator to (first) occurence of this chunk. + iterator to the end of the map if chunk pointer is not found */ + chunkVectIter getChild( Chunk* needle ); + + void replaceChildWithJunk( Chunk* child, bool deleteChild = true ); + + void changesAndSize( RIFF_MetaHandler* handler ); + std::string toString(XMP_Uns8 level = 0); + void write( RIFF_MetaHandler* handler, LFA_FileRef file, bool isMainChunk = false ); + + // destroy + void release(); // used by destructor and on error in constructor + ~ContainerChunk(); + + }; // class ContainerChunk + +} // namespace RIFF + + +#endif // __RIFF_hpp__ diff --git a/source/XMPFiles/FormatSupport/RIFF_Support.cpp b/source/XMPFiles/FormatSupport/RIFF_Support.cpp index 9574119..fe7e568 100644 --- a/source/XMPFiles/FormatSupport/RIFF_Support.cpp +++ b/source/XMPFiles/FormatSupport/RIFF_Support.cpp @@ -1,851 +1,930 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= -#include "RIFF_Support.hpp" +#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#if XMP_WinBuild - #pragma warning ( disable : 4996 ) // '...' was declared deprecated -#endif +// must have access to handler class fields... +#include "RIFF.hpp" +#include "RIFF_Handler.hpp" +#include "RIFF_Support.hpp" +#include "Reconcile_Impl.hpp" -namespace RIFF_Support { +using namespace RIFF; +namespace RIFF { - #define ckidPremierePadding MakeFourCC ('J','U','N','Q') - #define formtypeAVIX MakeFourCC ('A', 'V', 'I', 'X') +// The minimum BEXT chunk size should be 610 (incl. 8 byte header/size field) +XMP_Int32 MIN_BEXT_SIZE = 610; // = > 8 + ( 256+32+32+10+8+4+4+2+64+190+0 ) +// An assumed secure max value of 100 MB. +XMP_Int32 MAX_BEXT_SIZE = 100 * 1024 * 1024; - #ifndef AVIMAXCHUNKSIZE - #define AVIMAXCHUNKSIZE ((UInt32) 0x80000000) /* 2 GB */ - #endif +// CR8R, PrmL have fixed sizes +XMP_Int32 CR8R_SIZE = 0x5C; +XMP_Int32 PRML_SIZE = 0x122; +static const char* sHexChars = "0123456789ABCDEF"; - typedef struct - { - long id; - UInt32 len; - } atag; +// Encode a string of raw data bytes into a HexString (w/o spaces, i.e. "DEADBEEF"). +// No insertation/acceptance of whitespace/linefeeds. No output/tolerance of lowercase. +// returns true, if *all* characters returned are zero (or if 0 bytes are returned). +static bool EncodeToHexString ( XMP_StringPtr rawStr, + XMP_StringLen rawLen, + std::string* encodedStr ) +{ + bool allZero = true; // assume for now - // Local function declarations - static bool ReadTag ( LFA_FileRef inFileRef, long * outTag, UInt32 * outLength, long * subtype, UInt64 & inOutPosition, UInt64 maxOffset ); - static void AddTag ( RiffState & inOutRiffState, long tag, UInt32 len, UInt64 & inOutPosition, long parentID, long parentnum, long subtypeID ); - static long SubRead ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long parentid, UInt32 parentlen, UInt64 & inOutPosition ); - static bool ReadChunk ( LFA_FileRef inFileRef, UInt64 & pos, UInt32 len, char * outBuffer ); + if ( (rawStr == 0) && (rawLen != 0) ) + XMP_Throw ( "EncodeToHexString: null rawStr", kXMPErr_BadParam ); + if ( encodedStr == 0 ) + XMP_Throw ( "EncodeToHexString: null encodedStr", kXMPErr_BadParam ); - #define GetFilePosition(file) LFA_Seek ( file, 0, SEEK_CUR ) - - // ============================================================================================= + encodedStr->erase(); + if ( rawLen == 0 ) return allZero; + encodedStr->reserve ( rawLen * 2 ); - bool GetMetaData ( LFA_FileRef inFileRef, long tagID, char * outBuffer, unsigned long * outBufferSize ) + for( XMP_Uns32 i = 0; i < rawLen; i++ ) { - RiffState riffState; - - long numTags = OpenRIFF ( inFileRef, riffState ); - if ( numTags == 0 ) return false; - - return GetRIFFChunk ( inFileRef, riffState, tagID, 0, 0, outBuffer, outBufferSize ); - - } + // first, second nibble + XMP_Uns8 first = rawStr[i] >> 4; + XMP_Uns8 second = rawStr[i] & 0xF; - // ============================================================================================= + if ( allZero && (( first != 0 ) || (second != 0))) + allZero = false; - bool SetMetaData ( LFA_FileRef inFileRef, long riffType, long tagID, const char * inBuffer, unsigned long inBufferSize ) - { - RiffState riffState; - - long numTags = OpenRIFF ( inFileRef, riffState ); - if ( numTags == 0 ) return false; - - return PutChunk ( inFileRef, riffState, riffType, tagID, inBuffer, inBufferSize ); - + encodedStr->append( 1, sHexChars[first] ); + encodedStr->append( 1, sHexChars[second] ); } - // ============================================================================================= + return allZero; +} // EncodeToHexString - bool MarkChunkAsPadding ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, long tagID, long subtypeID ) - { - UInt32 len; - UInt64 pos; - atag tag; - - try { - - bool found = FindChunk ( inOutRiffState, tagID, riffType, subtypeID, NULL, &len, &pos ); - if ( ! found ) return false; - - if ( subtypeID != 0 ) { - pos -= 12; - } else { - pos -= 8; - } +// ------------------------------------------------------------------------------------------------- +// DecodeFromHexString +// ---------------- +// +// Decode a hex string to raw data bytes. +// * Input must be all uppercase and w/o any whitespace, strictly (0-9A-Z)* (i.e. "DEADBEEF0099AABC") +// * No insertation/acceptance of whitespace/linefeeds. +// * bNo use/tolerance of lowercase. +// * Number of bytes in the encoded String must be even. +// * returns true if everything went well, false if illegal (non 0-9A-F) character encountered + +static bool DecodeFromHexString ( XMP_StringPtr encodedStr, + XMP_StringLen encodedLen, + std::string* rawStr ) +{ + if ( (encodedLen % 2) != 0 ) + return false; + rawStr->erase(); + if ( encodedLen == 0 ) return true; + rawStr->reserve ( encodedLen / 2 ); + + for( XMP_Uns32 i = 0; i < encodedLen; ) + { + XMP_Uns8 upperNibble = encodedStr[i]; + if ( (upperNibble < 48) || ( (upperNibble > 57 ) && ( upperNibble < 65 ) ) || (upperNibble > 70) ) + return false; + if ( upperNibble >= 65 ) + upperNibble -= 7; // shift A-F area adjacent to 0-9 + upperNibble -= 48; // 'shift' to a value [0..15] + upperNibble = ( upperNibble << 4 ); + i++; + + XMP_Uns8 lowerNibble = encodedStr[i]; + if ( (lowerNibble < 48) || ( (lowerNibble > 57 ) && ( lowerNibble < 65 ) ) || (lowerNibble > 70) ) + return false; + if ( lowerNibble >= 65 ) + lowerNibble -= 7; // shift A-F area adjacent to 0-9 + lowerNibble -= 48; // 'shift' to a value [0..15] + i++; - tag.id = MakeUns32LE ( ckidPremierePadding ); - LFA_Seek ( inFileRef, pos, SEEK_SET ); - LFA_Write ( inFileRef, &tag, 4 ); - - pos += 8; - AddTag ( inOutRiffState, ckidPremierePadding, len, pos, 0, 0, 0 ); - - } catch(...) { + rawStr->append ( 1, (upperNibble + lowerNibble) ); + } + return true; +} // DecodeFromHexString + +// Converts input string to an ascii output string +// - terminates at first 0 +// - replaces all non ascii with 0x3F ('?') +// - produces up to maxOut characters (note that several UTF-8 character bytes can 'melt' to one byte '?' in ascii.) +static XMP_StringLen convertToASCII( XMP_StringPtr input, XMP_StringLen inputLen, std::string* output, XMP_StringLen maxOutputLen ) +{ + if ( (input == 0) && (inputLen != 0) ) + XMP_Throw ( "convertToASCII: null input string", kXMPErr_BadParam ); + if ( output == 0) + XMP_Throw ( "convertToASCII: null output string", kXMPErr_BadParam ); + if ( maxOutputLen == 0) + XMP_Throw ( "convertToASCII: zero maxOutputLen chars", kXMPErr_BadParam ); - return false; // If a write fails, it throws, so we return false. + output->reserve(inputLen); + output->erase(); + bool isUTF8 = ReconcileUtils::IsUTF8( input, inputLen ); + XMP_StringLen outputLen = 0; + + for ( XMP_Uns32 i=0; i < inputLen; i++ ) + { + XMP_Uns8 c = (XMP_Uns8) input[i]; + if ( c == 0 ) // early 0 termination, leave. + break; + if ( c > 127 ) // uft-8 multi-byte sequence. + { + if ( isUTF8 ) // skip all high bytes + { + // how many bytes in this ? + if ( c >= 0xC2 && c <= 0xDF ) + i+=1; // 2-byte sequence + else if ( c >= 0xE0 && c <= 0xEF ) + i+=2; // 3-byte sequence + else if ( c >= 0xF0 && c <= 0xF4 ) + i+=3; // 4-byte sequence + else + continue; //invalid sequence, look for next 'low' byte .. + } // thereafter and 'else': just append a question mark: + output->append( 1, '?' ); } - - return true; + else // regular valid ascii. 1 byte. + { + output->append( 1, input[i] ); + } + outputLen++; + if ( outputLen >= maxOutputLen ) + break; // (may be even or even greater due to UFT-8 multi-byte jumps) } - // ============================================================================================= + return outputLen; +} - bool PutChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, long tagID, const char * inBuffer, UInt32 inBufferSize ) +/** + * ensures that native property gets returned as UTF-8 (may or mayn not already be UTF-8) + * - also takes care of "moot padding" (pre-mature zero termination) + * - propertyExists: it is important to know if there as an existing, non zero property + * even (in the event of serverMode) it is not actually returned, but an empty string instead. + */ +static std::string nativePropertyToUTF8 ( XMP_StringPtr cstring, XMP_StringLen maxSize, bool* propertyExists ) +{ + // the value might be properly 0-terminated, prematurely or not + // at all, hence scan through to find actual size + XMP_StringLen size = 0; + for ( size = 0; size < maxSize; size++ ) { - UInt32 len; - UInt64 pos; - atag tag; - - // Make sure we're writting an even number of bytes. Required by the RIFF specification. - XMP_Assert ( (inBufferSize & 1) == 0 ); - - try { - - bool found = FindChunk ( inOutRiffState, tagID, 0, 0, NULL, &len, &pos ); - if ( found ) { + if ( cstring[size] == 0 ) + break; + } - if ( len == inBufferSize ) { - LFA_Seek ( inFileRef, pos, SEEK_SET ); - LFA_Write ( inFileRef, inBuffer, inBufferSize ); - return true; - } - - pos -= 8; - tag.id = MakeUns32LE ( ckidPremierePadding ); - LFA_Seek ( inFileRef, pos, SEEK_SET ); - LFA_Write ( inFileRef, &tag, 4 ); - - if ( len > inBufferSize ) { - pos += 8; - AddTag ( inOutRiffState, ckidPremierePadding, len, pos, 0, 0, 0 ); - } + (*propertyExists) = ( size > 0 ); + + std::string utf8(""); + if ( ReconcileUtils::IsUTF8( cstring, size ) ) + utf8 = std::string( cstring, size ); //use utf8 directly + else + { + if ( ! ignoreLocalText ) + { + #if ! UNIX_ENV // n/a anyway, since always ignoreLocalText on Unix + ReconcileUtils::LocalToUTF8( cstring, size, &utf8 ); + #endif + } + } + return utf8; +} - } +// reads maxSize bytes from file (not "up to", exactly fullSize) +// puts it into a string, sets respective tree property +static std::string getBextField ( const char* data, XMP_Uns32 offset, XMP_Uns32 maxSize ) +{ + if (data == 0) + XMP_Throw ( "getBextField: null data pointer", kXMPErr_BadParam ); + if ( maxSize == 0) + XMP_Throw ( "getBextField: maxSize must be greater than 0", kXMPErr_BadParam ); + + std::string r; + convertToASCII( data+offset, maxSize, &r, maxSize ); + return r; +} - } catch ( ... ) { +static void importBextChunkToXMP( RIFF_MetaHandler* handler, ValueChunk* bextChunk ) +{ + // if there's a bext chunk, there is data... + handler->containsXMP = true; // very important for treatment on caller level + + XMP_Enforce( bextChunk->oldSize >= MIN_BEXT_SIZE ); + XMP_Enforce( bextChunk->oldSize < MAX_BEXT_SIZE ); + + const char* data = bextChunk->oldValue.data(); + std::string value; + + // register bext namespace: + SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 ); + + // bextDescription ------------------------------------------------ + value = getBextField( data, 0, 256 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextDescription.ns, bextDescription.prop, value.c_str() ); + + // bextOriginator ------------------------------------------------- + value = getBextField( data, 256, 32 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginator.ns , bextOriginator.prop, value.c_str() ); + + // bextOriginatorRef ---------------------------------------------- + value = getBextField( data, 256+32, 32 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, value.c_str() ); + + // bextOriginationDate -------------------------------------------- + value = getBextField( data, 256+32+32, 10 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginationDate.ns , bextOriginationDate.prop, value.c_str() ); + + // bextOriginationTime -------------------------------------------- + value = getBextField( data, 256+32+32+10, 8 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginationTime.ns , bextOriginationTime.prop, value.c_str() ); + + // bextTimeReference ---------------------------------------------- + // thanx to nice byte order, all 8 bytes can be read as one: + XMP_Uns64 timeReferenceFull = GetUns64LE( &(data[256+32+32+10+8 ] ) ); + value.erase(); + SXMPUtils::ConvertFromInt64( timeReferenceFull, "%llu", &value ); + handler->xmpObj.SetProperty( bextTimeReference.ns, bextTimeReference.prop, value ); + + // bextVersion ---------------------------------------------------- + XMP_Uns16 bwfVersion = GetUns16LE( &(data[256+32+32+10+8+8] ) ); + value.erase(); + SXMPUtils::ConvertFromInt( bwfVersion, "", &value ); + handler->xmpObj.SetProperty( bextVersion.ns, bextVersion.prop, value ); + + // bextUMID ------------------------------------------------------- + // binary string is already in memory, must convert to hex string + std::string umidString; + bool allZero = EncodeToHexString( &(data[256+32+32+10+8+8+2]), 64, &umidString ); + if (! allZero ) + handler->xmpObj.SetProperty( bextUMID.ns, bextUMID.prop, umidString ); + + // bextCodingHistory ---------------------------------------------- + bool hasCodingHistory = bextChunk->oldSize > MIN_BEXT_SIZE; + + if ( hasCodingHistory ) + { + XMP_StringLen codingHistorySize = (XMP_StringLen) (bextChunk->oldSize - MIN_BEXT_SIZE); + std::string codingHistory; + convertToASCII( &data[MIN_BEXT_SIZE-8], codingHistorySize, &codingHistory, codingHistorySize ); + if (! codingHistory.empty() ) + handler->xmpObj.SetProperty( bextCodingHistory.ns, bextCodingHistory.prop, codingHistory ); + } +} // importBextChunkToXMP - // If a write fails, it throws, so we return false - return false; +static void importPrmLToXMP( RIFF_MetaHandler* handler, ValueChunk* prmlChunk ) +{ + bool haveXMP = false; - } - - bool ok = MakeChunk ( inFileRef, inOutRiffState, riffType, (inBufferSize + 8) ); - if ( ! ok ) return false; - - return WriteChunk ( inFileRef, tagID, inBuffer, inBufferSize ); + XMP_Enforce( prmlChunk->oldSize == PRML_SIZE ); + PrmLBoxContent rawPrmL; + XMP_Assert ( sizeof ( rawPrmL ) == PRML_SIZE - 8 ); // double check tight packing. + XMP_Assert ( sizeof ( rawPrmL.filePath ) == 260 ); + memcpy ( &rawPrmL, prmlChunk->oldValue.data(), sizeof (rawPrmL) ); + if ( rawPrmL.magic != 0xBEEFCAFE ) { + Flip4 ( &rawPrmL.exportType ); // The only numeric field that we care about. } - // ============================================================================================= - - bool RewriteChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long tagID, long parentID, const char * inData ) - { - UInt32 len; - UInt64 pos; - - try { - if ( FindChunk ( inOutRiffState, tagID, parentID, 0, NULL, &len, &pos ) ) { - LFA_Seek ( inFileRef, pos, SEEK_SET ); - LFA_Write ( inFileRef, inData, len ); - } - } catch ( ... ) { - return false; + rawPrmL.filePath[259] = 0; // Ensure a terminating nul. + if ( rawPrmL.filePath[0] != 0 ) { + if ( rawPrmL.filePath[0] == '/' ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", + kXMP_NS_CreatorAtom, "posixProjectPath", rawPrmL.filePath ); + } else if ( XMP_LitNMatch ( rawPrmL.filePath, "\\\\?\\", 4 ) ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", + kXMP_NS_CreatorAtom, "uncProjectPath", rawPrmL.filePath ); } - - return true; - } - // ============================================================================================= - - bool MakeChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, UInt32 len ) - { - long starttag, taglen; - UInt32 rifflen, avail; - UInt64 pos; - - /* look for top level Premiere padding chunk */ - starttag = 0; - while ( FindChunk ( inOutRiffState, ckidPremierePadding, riffType, 0, &starttag, reinterpret_cast<unsigned long*>(&taglen), &pos ) ) { - - pos -= 8; - taglen += 8; - long extra = taglen - len; - if ( extra < 0 ) continue; - - RiffIterator iter = inOutRiffState.tags.begin(); - iter += (starttag - 1); + const char * exportStr = 0; + switch ( rawPrmL.exportType ) { + case kExportTypeMovie : exportStr = "movie"; break; + case kExportTypeStill : exportStr = "still"; break; + case kExportTypeAudio : exportStr = "audio"; break; + case kExportTypeCustom : exportStr = "custom"; break; + } + if ( exportStr != 0 ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", exportStr ); + } - if ( extra == 0 ) { - - iter->len = 0; + handler->containsXMP |= haveXMP; // mind the '|=' +} // importCr8rToXMP - } else { +static void importCr8rToXMP( RIFF_MetaHandler* handler, ValueChunk* cr8rChunk ) +{ + bool haveXMP = false; - atag pad; - UInt64 padpos; + XMP_Enforce( cr8rChunk->oldSize == CR8R_SIZE ); + Cr8rBoxContent rawCr8r; + XMP_Assert ( sizeof ( rawCr8r ) == CR8R_SIZE - 8 ); // double check tight packing. + memcpy ( &rawCr8r, cr8rChunk->oldValue.data(), sizeof (rawCr8r) ); - /* need 8 bytes extra to be able to split it */ - extra -= 8; - if ( extra < 0 ) continue; - - try{ - padpos = pos + len; - LFA_Seek ( inFileRef, padpos, SEEK_SET ); - pad.id = MakeUns32LE ( ckidPremierePadding ); - pad.len = MakeUns32LE ( extra ); - LFA_Write ( inFileRef, &pad, sizeof(pad) ); - } catch ( ... ) { - return false; - } - - iter->pos = padpos + 8; - iter->len = extra; + if ( rawCr8r.magic != 0xBEEFCAFE ) { + Flip4 ( &rawCr8r.creatorCode ); // The only numeric fields that we care about. + Flip4 ( &rawCr8r.appleEvent ); + } - } + std::string fieldPath; - /* seek back to start of original padding chunk */ - LFA_Seek ( inFileRef, pos, SEEK_SET ); + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &fieldPath ); + if ( rawCr8r.creatorCode != 0 ) { + haveXMP = true; + handler->xmpObj.SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.creatorCode ); // ! Unsigned trickery. + } - return true; + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &fieldPath ); + if ( rawCr8r.appleEvent != 0 ) { + haveXMP = true; + handler->xmpObj.SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.appleEvent ); // ! Unsigned trickery. + } - } - - /* can't take padding chunk, so append new chunk to end of file */ - - rifflen = inOutRiffState.rifflen + 8; - avail = AVIMAXCHUNKSIZE - rifflen; - - LFA_Seek ( inFileRef, 0, SEEK_END ); - pos = GetFilePosition ( inFileRef ); - - if ( (pos & 1) == 1 ) { - // The file length is odd, need a pad byte. - XMP_Uns8 pad = 0; - LFA_Write ( inFileRef, &pad, 1 ); - ++pos; - } - - if ( avail < len ) { + rawCr8r.fileExt[15] = 0; // Ensure a terminating nul. + if ( rawCr8r.fileExt[0] != 0 ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", rawCr8r.fileExt ); + } - /* if needed, create new AVIX chunk */ - ltag avix; - - avix.id = MakeUns32LE ( FOURCC_RIFF ); - avix.len = MakeUns32LE ( 4 + len ); - avix.subid = MakeUns32LE ( formtypeAVIX ); - LFA_Write(inFileRef, &avix, sizeof(avix)); + rawCr8r.appOptions[15] = 0; // Ensure a terminating nul. + if ( rawCr8r.appOptions[0] != 0 ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", rawCr8r.appOptions ); + } + + rawCr8r.appName[31] = 0; // Ensure a terminating nul. + if ( rawCr8r.appName[0] != 0 ) { + haveXMP = true; + handler->xmpObj.SetProperty ( kXMP_NS_XMP, "CreatorTool", rawCr8r.appName ); + } - pos += 12; - AddTag ( inOutRiffState, avix.id, len, pos, 0, 0, 0 ); + handler->containsXMP |= haveXMP; // mind the '|=' +} // importCr8rToXMP - } else { - /* otherwise, rewrite length of last RIFF chunk in file */ - pos = inOutRiffState.riffpos + 4; - rifflen = inOutRiffState.rifflen + len; - XMP_Uns32 fileLen = MakeUns32LE ( rifflen ); - LFA_Seek ( inFileRef, pos, SEEK_SET ); - LFA_Write ( inFileRef, &fileLen, 4 ); - inOutRiffState.rifflen = rifflen; - - /* prepare to write data */ - LFA_Seek ( inFileRef, 0, SEEK_END ); +static void importListChunkToXMP( RIFF_MetaHandler* handler, ContainerChunk* listChunk, Mapping mapping[], bool xmpHasPriority ) +{ + valueMap* cm = &listChunk->childmap; + for (int p=0; mapping[p].chunkID != 0; p++) // go through legacy chunks + { + valueMapIter result = cm->find(mapping[p].chunkID); + if( result != cm->end() ) // if value found + { + ValueChunk* propChunk = result->second; + + bool propertyExists = false; + std::string utf8 = nativePropertyToUTF8( + propChunk->oldValue.c_str(), + (XMP_StringLen)propChunk->oldValue.size(), &propertyExists ); + + if ( utf8.size() > 0 ) // if property is not-empty, set Property + { + switch ( mapping[p].propType ) + { + case prop_TIMEVALUE: + if ( xmpHasPriority && + handler->xmpObj.DoesStructFieldExist( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue" )) + break; // skip if XMP has precedence and exists + handler->xmpObj.SetStructField( mapping[p].ns, mapping[p].prop, + kXMP_NS_DM, "timeValue", utf8.c_str() ); + break; + case prop_LOCALIZED_TEXT: + if ( xmpHasPriority && handler->xmpObj.GetLocalizedText( mapping[p].ns , + mapping[p].prop, "" , "x-default", 0, 0, 0 )) + break; // skip if XMP has precedence and exists + handler->xmpObj.SetLocalizedText( mapping[p].ns , mapping[p].prop, + "" , "x-default" , utf8.c_str() ); + if ( mapping[p].chunkID == kPropChunkINAM ) + handler->hasListInfoINAM = true; // needs to be known for special 3-way merge around dc:title + break; + case prop_ARRAYITEM: + if ( xmpHasPriority && + handler->xmpObj.DoesArrayItemExist( mapping[p].ns, mapping[p].prop, 1 )) + break; // skip if XMP has precedence and exists + handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop ); + handler->xmpObj.AppendArrayItem( mapping[p].ns, mapping[p].prop, kXMP_PropValueIsArray, utf8.c_str(), kXMP_NoOptions ); + break; + case prop_SIMPLE: + if ( xmpHasPriority && + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + break; // skip if XMP has precedence and exists + handler->xmpObj.SetProperty( mapping[p].ns, mapping[p].prop, utf8.c_str() ); + break; + default: + XMP_Throw( "internal error" , kXMPErr_InternalFailure ); + } + handler->containsXMP = true; // very important for treatment on caller level + } + else if ( ! propertyExists) // otherwise remove it. + { // [2389942] don't, if legacy value is existing but non-retrievable (due to server mode) + switch ( mapping[p].propType ) + { + case prop_TIMEVALUE: + if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop ); + break; + case prop_LOCALIZED_TEXT: + if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + handler->xmpObj.DeleteLocalizedText( mapping[p].ns, mapping[p].prop, "", "x-default" ); + break; + case prop_ARRAYITEM: + case prop_SIMPLE: + if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop ); + break; + default: + XMP_Throw( "internal error" , kXMPErr_InternalFailure ); + } + } } - - return true; - + } // for +} +void importProperties( RIFF_MetaHandler* handler ) +{ + bool hasDigest = handler->xmpObj.GetProperty( kXMP_NS_WAV, "NativeDigest", NULL , NULL ); + if ( hasDigest ) + { + // remove! since it now becomse a 'new' handler file + handler->xmpObj.DeleteProperty( kXMP_NS_WAV, "NativeDigest" ); } - // ============================================================================================= - - bool WriteChunk ( LFA_FileRef inFileRef, long tagID, const char * data, UInt32 len ) + // BWF Bext extension chunk ----------------------------------------------- + if ( handler->parent->format == kXMP_WAVFile && // applies only to WAV + handler->bextChunk != 0 ) //skip if no BEXT chunk found. { - atag ck; - ck.id = MakeUns32LE ( tagID ); - ck.len = MakeUns32LE ( len ); - - try { - LFA_Write ( inFileRef, &ck, 8 ); - LFA_Write ( inFileRef, data, len ); - } catch ( ... ) { - return false; - } - - return true; + importBextChunkToXMP( handler, handler->bextChunk ); } - // ============================================================================================= - - long OpenRIFF ( LFA_FileRef inFileRef, RiffState & inOutRiffState ) + // PrmL chunk -------------------------------------------------------------- + if ( handler->prmlChunk != 0 && handler->prmlChunk->oldSize == PRML_SIZE ) { - UInt64 pos = 0; - long tag, subtype; - UInt32 len; - - const XMP_Int64 fileLen = LFA_Measure ( inFileRef ); - if ( fileLen < 8 ) return 0; - - LFA_Seek ( inFileRef, 0, SEEK_SET ); - - while ( ReadTag ( inFileRef, &tag, &len, &subtype, pos, fileLen ) ) { - if ( tag != FOURCC_RIFF ) break; - AddTag ( inOutRiffState, tag, len, pos, 0, 0, subtype ); - if ( subtype != 0 ) SubRead ( inFileRef, inOutRiffState, subtype, len, pos ); - } - - return (long) inOutRiffState.tags.size(); - + importPrmLToXMP( handler, handler->prmlChunk ); } - // ============================================================================================= - - static bool ReadTag ( LFA_FileRef inFileRef, long * outTag, UInt32 * outLength, long * subtype, UInt64 & inOutPosition, UInt64 maxOffset ) + // Cr8r chunk -------------------------------------------------------------- + if ( handler->cr8rChunk != 0 && handler->cr8rChunk->oldSize == CR8R_SIZE ) { - UInt32 realLength; - - long bytesRead; - bytesRead = LFA_Read ( inFileRef, outTag, 4 ); - if ( bytesRead != 4 ) return false; - *outTag = GetUns32LE ( outTag ); - - bytesRead = LFA_Read ( inFileRef, outLength, 4 ); - if ( bytesRead != 4 ) return false; - *outLength = GetUns32LE ( outLength ); - - realLength = *outLength; - realLength += (realLength & 1); // Round up to an even value. - - inOutPosition = GetFilePosition ( inFileRef ); // The file offset of the data portion. - UInt64 maxLength = maxOffset - inOutPosition; - - if ( (inOutPosition > maxOffset) || ((UInt64)(*outLength) > maxLength) ) { - - bool ignoreLastPad = true; // Ignore cases where a final pad byte is missing. - UInt64 fileLen = LFA_Measure ( inFileRef ); - if ( inOutPosition > (maxOffset + 1) ) ignoreLastPad = false; - if ( (UInt64)(*outLength) > (maxLength + 1) ) ignoreLastPad = false; - - if ( ! ignoreLastPad ) { - - // Workaround for bad files in the field that have a bad size in the outermost RIFF - // chunk. Do a "runtime repair" of cases where the length is too long (beyond EOF). - // This handles read-only usage, update usage is repaired (or not) in the handler. - - bool oversizeRIFF = (inOutPosition == 8) && // Is this the initial 'RIFF' chunk? - (fileLen >= 8); // Is the file at least of the minimal size? - - if ( ! oversizeRIFF ) { - XMP_Throw ( "RIFF tag exceeds maximum length", kXMPErr_BadValue ); - } else { - *outLength = (UInt32)(fileLen) - 8; - realLength = *outLength; - realLength += (realLength & 1); // Round up to an even value. - } + importCr8rToXMP( handler, handler->cr8rChunk ); + } - } + // LIST:INFO -------------------------------------------------------------- + if ( handler->listInfoChunk != 0) //skip if no LIST:INFO chunk found. + importListChunkToXMP( handler, handler->listInfoChunk, listInfoProps, hasDigest ); - } - - *subtype = 0; + // LIST:Tdat -------------------------------------------------------------- + if ( handler->listTdatChunk != 0) + importListChunkToXMP( handler, handler->listTdatChunk, listTdatProps, hasDigest ); - if ( (*outTag != FOURCC_LIST) && (*outTag != FOURCC_RIFF) ) { + // DISP (do last, higher priority than INAM ) ----------------------------- + bool takeXMP = false; // assume for now + if ( hasDigest ) + { + std::string actualLang, value; + bool r = handler->xmpObj.GetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, &value, NULL ); + if ( r && (actualLang == "x-default") ) takeXMP = true; + } - UInt64 tempPos = inOutPosition + realLength; - if ( tempPos <= maxOffset ) { - LFA_Seek ( inFileRef, tempPos, SEEK_SET ); - } else if ( (tempPos == (maxOffset + 1)) && (maxOffset == (UInt64)LFA_Measure(inFileRef)) ) { - LFA_Seek ( inFileRef, 0, SEEK_END ); // Hack to tolerate a missing final pad byte. - } else { - XMP_Throw ( "Bad RIFF offset", kXMPErr_BadValue ); + if ( (!takeXMP) && handler->dispChunk != 0) //skip if no LIST:INFO chunk found. + { + std::string* value = &handler->dispChunk->oldValue; + if ( value->size() >= 4 ) // ignore contents if file too small + { + XMP_StringPtr cstring = value->c_str(); + XMP_StringLen size = (XMP_StringLen) value->size(); + + size -= 4; // skip first four bytes known to contain constant + cstring += 4; + + bool propertyExists = false; + std::string utf8 = nativePropertyToUTF8( cstring, size, &propertyExists ); + + if ( utf8.size() > 0 ) + { + handler->xmpObj.SetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , utf8.c_str() ); + handler->containsXMP = true; // very important for treatment on caller level } - - } else { - - bytesRead = LFA_Read ( inFileRef, subtype, 4 ); - if ( bytesRead != 4 ) return false; - *subtype = GetUns32LE ( subtype ); - - *outLength -= 4; - realLength -= 4; - - // Special case: - // Since the 'movi' chunk can contain billions of subchunks, skip over the 'movi' subchunk. - // - // The 'movi' subtype is added to the list as the TAG. - // The subtype is returned empty so nobody will try to parse the subchunks. - - if ( *subtype == listtypeAVIMOVIE ) { - inOutPosition = GetFilePosition ( inFileRef ); - UInt64 tempPos = inOutPosition + realLength; - if ( tempPos <= maxOffset ) { - LFA_Seek ( inFileRef, tempPos, SEEK_SET ); - } else if ( (tempPos == (maxOffset + 1)) && (maxOffset == (UInt64)LFA_Measure(inFileRef)) ) { - LFA_Seek ( inFileRef, 0, SEEK_END ); // Hack to tolerate a missing final pad byte. - } else { - XMP_Throw ( "Bad RIFF offset", kXMPErr_BadValue ); + else + { + // found as part of [2389942] + // forward deletion may only happen if no LIST:INFO/INAM is present: + if ( ! handler->hasListInfoINAM && + ! propertyExists ) // ..[2389942]part2: and if truly no legacy property + { // (not just an unreadable one due to ServerMode). + handler->xmpObj.DeleteProperty( kXMP_NS_DC, "title" ); } - *outLength += 4; - *outTag = *subtype; - *subtype = 0; } + } // if size sufficient + } // handler->dispChunk - inOutPosition = GetFilePosition ( inFileRef ); +} // importProperties - } +//////////////////////////////////////////////////////////////////////////////// +// EXPORT +//////////////////////////////////////////////////////////////////////////////// + +void relocateWronglyPlacedXMPChunk( RIFF_MetaHandler* handler ) +{ + LFA_FileRef file = handler->parent->fileRef; + RIFF::containerVect *rc = &handler->riffChunks; + RIFF::ContainerChunk* lastChunk = rc->at( rc->size()-1 ); + + // 1) XMPPacket + // needChunk exists but is not in lastChunk ? + if ( + handler->xmpChunk != 0 && // XMP Chunk existing? + (XMP_Uns32)rc->size() > 1 && // more than 1 top-level chunk (otherwise pointless) + lastChunk->getChild( handler->xmpChunk ) == lastChunk->children.end() // not already in last chunk? + ) + { + RIFF::ContainerChunk* cur; + chunkVectIter child; + XMP_Int32 chunkNo; + + // find and relocate to last chunk: + for ( chunkNo = (XMP_Int32)rc->size()-2 ; chunkNo >= 0; chunkNo-- ) // ==> start with second-last chunk + { + cur = rc->at(chunkNo); + child = cur->getChild( handler->xmpChunk ); + if ( child != cur->children.end() ) // found? + break; + } // for + + if ( chunkNo < 0 ) // already in place? nothing left to do. + return; + + lastChunk->children.push_back( *child ); // nb: order matters! + cur->replaceChildWithJunk( *child, false ); + cur->hasChange = true; // [2414649] initialize early-on i.e: here + } // if +} // relocateWronglyPlacedXMPChunk + +// writes to buffer up to max size, +// 0 termination only if shorter than maxSize +// converts down to ascii +static void setBextField ( std::string* value, XMP_Uns8* data, XMP_Uns32 offset, XMP_Uns32 maxSize ) +{ + XMP_Validate( value != 0, "setBextField: null value string pointer", kXMPErr_BadParam ); + XMP_Validate( data != 0, "setBextField: null data value", kXMPErr_BadParam ); + XMP_Validate( maxSize > 0, "setBextField: maxSize must be greater than 0", kXMPErr_BadParam ); - return true; + std::string ascii; + XMP_StringLen actualSize = convertToASCII( value->data(), (XMP_StringLen) value->size() , &ascii , maxSize ); + strncpy( (char*)(data + offset), ascii.data(), actualSize ); +} - } +// add bwf-bext related data to bext chunk, create if not existing yet. +// * in fact, since bext is fully fixed and known, there can be no unknown subchunks worth keeping: +// * prepare bext chunk in buffer +// * value changed/created if needed only, otherways remove chunk +// * remove bext-mapped properties from xmp (non-redundant storage) +// note: ValueChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation) +static void exportXMPtoBextChunk( RIFF_MetaHandler* handler, ValueChunk** bextChunk ) +{ + // register bext namespace ( if there was no import, this is news, otherwise harmless moot) + SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 ); - // ============================================================================================= + bool chunkUsed = false; // assume for now + SXMPMeta* xmp = &handler->xmpObj; - static void AddTag ( RiffState & inOutRiffState, long tag, UInt32 len, UInt64 & inOutPosition, long parentID, long parentnum, long subtypeID ) + // prepare buffer, need to know CodingHistory size as the only variable + XMP_Int32 bextBufferSize = MIN_BEXT_SIZE - 8; // -8 because of header + std::string value; + if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, kXMP_NoOptions )) { - RiffTag newTag; - - newTag.pos = inOutPosition; - newTag.tagID = tag; - newTag.len = len; - newTag.parent = parentnum; - newTag.parentID = parentID; - newTag.subtypeID = subtypeID; - - inOutRiffState.tags.push_back ( newTag ); - - if ( tag == FOURCC_RIFF ) { - inOutRiffState.riffpos = inOutPosition - 12; - inOutRiffState.rifflen = len + 4; - } - + bextBufferSize += ((XMP_StringLen)value.size()) + 1 ; // add to size (and a trailing zero) } - // ============================================================================================= + // create and clear buffer + XMP_Uns8* buffer = new XMP_Uns8[bextBufferSize]; + for (XMP_Int32 i = 0; i < bextBufferSize; i++ ) + buffer[i] = 0; - static long SubRead ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long parentid, UInt32 parentlen, UInt64 & inOutPosition ) + // grab props, write into buffer, remove from XMP /////////////////////////// + // bextDescription ------------------------------------------------ + if ( xmp->GetProperty( bextDescription.ns, bextDescription.prop, &value, kXMP_NoOptions ) ) { - long tag; - long subtype = 0; - long parentnum; - UInt32 len, total, childlen; - UInt64 oldpos; - - total = 0; - parentnum = (long) inOutRiffState.tags.size() - 1; - - UInt64 maxOffset = inOutPosition + parentlen; - - while ( parentlen > 0 ) { - - oldpos = inOutPosition; - ReadTag ( inFileRef, &tag, &len, &subtype, inOutPosition, maxOffset ); - AddTag ( inOutRiffState, tag, len, inOutPosition, parentid, parentnum, subtype ); - len += (len & 1); //padding byte - - if ( subtype == 0 ) { - childlen = 8 + len; - } else { - childlen = 12 + SubRead ( inFileRef, inOutRiffState, subtype, len, inOutPosition ); - } - - if ( parentlen < childlen ) parentlen = childlen; - parentlen -= childlen; - total += childlen; - - } - - return total; - + setBextField( &value, (XMP_Uns8*) buffer, 0, 256 ); + xmp->DeleteProperty( bextDescription.ns, bextDescription.prop) ; + chunkUsed = true; } - - // ============================================================================================= - - bool GetRIFFChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long tagID, - long parentID, long subtypeID, char * outBuffer, unsigned long * outBufferSize, - UInt64* posPtr ) + // bextOriginator ------------------------------------------------- + if ( xmp->GetProperty( bextOriginator.ns , bextOriginator.prop, &value, kXMP_NoOptions ) ) { - UInt32 len; - UInt64 pos; - - bool found = FindChunk ( inOutRiffState, tagID, parentID, subtypeID, 0, &len, &pos ); - if ( ! found ) return false; - - if ( posPtr != 0 ) - *posPtr = pos; // return position CBR - - if ( outBuffer == 0 ) { - *outBufferSize = (unsigned long)len; - return true; // Found, but not wanted. - } - - if ( len > *outBufferSize ) - len = *outBufferSize; - found = ReadChunk ( inFileRef, pos, len, outBuffer ); - - return found; + setBextField( &value, (XMP_Uns8*) buffer, 256, 32 ); + xmp->DeleteProperty( bextOriginator.ns , bextOriginator.prop ); + chunkUsed = true; + } + // bextOriginatorRef ---------------------------------------------- + if ( xmp->GetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, &value, kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256+32, 32 ); + xmp->DeleteProperty( bextOriginatorRef.ns , bextOriginatorRef.prop ); + chunkUsed = true; + } + // bextOriginationDate -------------------------------------------- + if ( xmp->GetProperty( bextOriginationDate.ns , bextOriginationDate.prop, &value, kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256+32+32, 10 ); + xmp->DeleteProperty( bextOriginationDate.ns , bextOriginationDate.prop ); + chunkUsed = true; + } + // bextOriginationTime -------------------------------------------- + if ( xmp->GetProperty( bextOriginationTime.ns , bextOriginationTime.prop, &value, kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256+32+32+10, 8 ); + xmp->DeleteProperty( bextOriginationTime.ns , bextOriginationTime.prop ); + chunkUsed = true; + } + // bextTimeReference ---------------------------------------------- + // thanx to friendly byte order, all 8 bytes can be written in one go: + if ( xmp->GetProperty( bextTimeReference.ns, bextTimeReference.prop, &value, kXMP_NoOptions ) ) + { + try + { + XMP_Int64 v = SXMPUtils::ConvertToInt64( value.c_str() ); + PutUns64LE( v, &(buffer[256+32+32+10+8] )); + chunkUsed = true; + } + catch (XMP_Error& e) + { + if ( e.GetID() != kXMPErr_BadParam ) + throw e; // re-throw on any other error + } // 'else' tolerate ( time reference remains 0x00000000 ) + // valid or not, do not store redundantly: + xmp->DeleteProperty( bextTimeReference.ns, bextTimeReference.prop ); } - // ============================================================================================= + // bextVersion ---------------------------------------------------- + // set version=1, no matter what. + PutUns16LE( 1, &(buffer[256+32+32+10+8+8]) ); + xmp->DeleteProperty( bextVersion.ns, bextVersion.prop ); - bool FindChunk ( RiffState & inOutRiffState, long tagID, long parentID, long subtypeID, - long * startTagIndex, UInt32 * len, UInt64 * pos) + // bextUMID ------------------------------------------------------- + if ( xmp->GetProperty( bextUMID.ns, bextUMID.prop, &value, kXMP_NoOptions ) ) { - std::vector<RiffTag>::iterator iter = inOutRiffState.tags.begin(); - std::vector<RiffTag>::iterator endIter = inOutRiffState.tags.end(); - - // If we're using the next index, skip the iterator. - if ( startTagIndex != 0 ) iter += *startTagIndex; + std::string rawStr; - for ( ; iter != endIter ; ++iter ) { + if ( !DecodeFromHexString( value.data(), (XMP_StringLen) value.size(), &rawStr ) ) + { + delete [] buffer; // important. + XMP_Throw ( "EncodeFromHexString: illegal umid string. Must contain an even number of 0-9 and uppercase A-F chars.", kXMPErr_BadParam ); + } - if ( startTagIndex != 0 ) *startTagIndex += 1; - - if ( (parentID!= 0) && (iter->parentID != parentID) ) continue; - if ( (tagID != 0) && (iter->tagID != tagID) ) continue; - if ( (subtypeID != 0) && (iter->subtypeID != subtypeID) ) continue; - - if ( len != 0 ) *len = iter->len; - if ( pos != 0 ) *pos = iter->pos; - - return true; + // if UMID is smaller/longer than 64 byte for any reason, + // truncate/do a partial write (just like for any other bext property) - } - - return false; + memcpy( (char*) &(buffer[256+32+32+10+8+8+2]), rawStr.data(), MIN( 64, rawStr.size() ) ); + xmp->DeleteProperty( bextUMID.ns, bextUMID.prop ); + chunkUsed = true; } - // ============================================================================================= - - static bool ReadChunk ( LFA_FileRef inFileRef, UInt64 & pos, UInt32 len, char * outBuffer ) + // bextCodingHistory ---------------------------------------------- + if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, kXMP_NoOptions ) ) { - - if ( (inFileRef == 0) || (outBuffer == 0) ) return false; - - LFA_Seek (inFileRef, pos, SEEK_SET ); - UInt32 bytesRead = LFA_Read ( inFileRef, outBuffer, len ); - if ( bytesRead != len ) return false; - - return true; - + std::string ascii; + convertToASCII( value.data(), (XMP_StringLen) value.size() , &ascii, (XMP_StringLen) value.size() ); + strncpy( (char*) &(buffer[MIN_BEXT_SIZE-8]), ascii.data(), ascii.size() ); + xmp->DeleteProperty( bextCodingHistory.ns, bextCodingHistory.prop ); + chunkUsed = true; } -} // namespace RIFF_Support - -// ================================================================================================= - -// *** Could be moved to a separate header - -#pragma pack(push,1) - -// [TODO] Can we switch to using just a full path here? -struct FSSpecLegacy -{ - short vRefNum; - long parID; - char name[260]; // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp -- 260 is "old school", 32000 is "new school". -}; - -struct CR8R_CreatorAtom -{ - unsigned long magicLu; - - long atom_sizeL; // Size of this structure. - short atom_vers_majorS; // Major atom version. - short atom_vers_minorS; // Minor atom version. - - // mac - unsigned long creator_codeLu; // Application code on MacOS. - unsigned long creator_eventLu; // Invocation appleEvent. - - // windows - char creator_extAC[16]; // Extension allowing registry search to app. - char creator_flagAC[16]; // Flag passed to app at invocation time. - - char creator_nameAC[32]; // Name of the creator application. -}; - -typedef CR8R_CreatorAtom** CR8R_CreatorAtomHandle; + // always delete old, recreate if needed + if ( *bextChunk != 0 ) + { + (*bextChunk)->parent->replaceChildWithJunk( *bextChunk ); + (*bextChunk) = 0; // clear direct Chunk pointer + } -#define PR_PROJECT_LINK_MAGIC 0x600DF00D // GoodFood + if ( chunkUsed) + *bextChunk = new ValueChunk( handler->riffChunks.at(0), std::string( (char*)buffer, bextBufferSize ), kChunk_bext ); -typedef enum -{ - Embed_ExportTypeMovie = 0, - Embed_ExportTypeStill, - Embed_ExportTypeAudio, - Embed_ExportTypeCustom + delete [] buffer; // important. } -Embed_ExportType; - -struct Embed_ProjectLinkAtom +static inline void SetBufferedString ( char * dest, const std::string source, size_t limit ) { - unsigned long magicLu; - long atom_sizeL; - short atom_vers_apiS; - short atom_vers_codeS; - unsigned long exportType; // See enum. The type of export that generated the file - FSSpecLegacy fullPath; // Full path of the project file -}; - -#pragma pack(pop) - -// ------------------------------------------------------------------------------------------------- - -#define kCreatorTool "CreatorTool" -#define AdobeCreatorAtomVersion_Major 1 -#define AdobeCreatorAtomVersion_Minor 0 -#define AdobeCreatorAtom_Magic 0xBEEFCAFE - -#define myCreatorAtom MakeFourCC ( 'C','r','8','r' ) + memset ( dest, 0, limit ); + size_t count = source.size(); + if ( count >= limit ) count = limit - 1; // Ensure a terminating nul. + memcpy ( dest, source.c_str(), count ); +} -static void CreatorAtom_Initialize ( CR8R_CreatorAtom& creatorAtom ) +static void exportXMPtoCr8rChunk ( RIFF_MetaHandler* handler, ValueChunk** cr8rChunk ) { - memset ( &creatorAtom, 0, sizeof(CR8R_CreatorAtom) ); - creatorAtom.magicLu = AdobeCreatorAtom_Magic; - creatorAtom.atom_vers_majorS = AdobeCreatorAtomVersion_Major; - creatorAtom.atom_vers_minorS = AdobeCreatorAtomVersion_Minor; - creatorAtom.atom_sizeL = sizeof(CR8R_CreatorAtom); -} + const SXMPMeta & xmp = handler->xmpObj; + + // Make sure an existing Cr8r chunk has the proper fixed length. + bool haveOldCr8r = (*cr8rChunk != 0); + if ( haveOldCr8r && ((*cr8rChunk)->oldSize != sizeof(Cr8rBoxContent)+8) ) { + (*cr8rChunk)->parent->replaceChildWithJunk ( *cr8rChunk ); // Wrong length, the existing chunk must be bad. + (*cr8rChunk) = 0; + haveOldCr8r = false; + } -// ------------------------------------------------------------------------------------------------- + bool haveNewCr8r = false; + std::string creatorCode, appleEvent, fileExt, appOptions, appName; + + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &creatorCode, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &appleEvent, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fileExt, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &appOptions, 0 ); + haveNewCr8r |= xmp.GetProperty ( kXMP_NS_XMP, "CreatorTool", &appName, 0 ); -static void CreatorAtom_MakeValid ( CR8R_CreatorAtom * creator_atomP ) -{ - // If already valid, no conversion is needed. - if ( creator_atomP->magicLu == AdobeCreatorAtom_Magic ) return; + if ( ! haveNewCr8r ) { // Get rid of an existing Cr8r chunk if there is no new XMP. + if ( haveOldCr8r ) { + (*cr8rChunk)->parent->replaceChildWithJunk ( *cr8rChunk ); + *cr8rChunk = 0; + } + return; + } + + if ( ! haveOldCr8r ) { + *cr8rChunk = new ValueChunk ( handler->lastChunk, std::string(), kChunk_Cr8r ); + } + + std::string strValue; + strValue.assign ( (sizeof(Cr8rBoxContent) - 1), '\0' ); // ! Use size-1 because SetValue appends a trailing 0 byte. + (*cr8rChunk)->SetValue ( strValue ); // ! Just get the space available. + XMP_Assert ( (*cr8rChunk)->newValue.size() == sizeof(Cr8rBoxContent) ); + (*cr8rChunk)->hasChange = true; - Flip4 ( &creator_atomP->magicLu ); - Flip2 ( &creator_atomP->atom_vers_majorS ); - Flip2 ( &creator_atomP->atom_vers_minorS ); + Cr8rBoxContent * newCr8r = (Cr8rBoxContent*) (*cr8rChunk)->newValue.data(); + + if ( ! haveOldCr8r ) { - Flip4 ( &creator_atomP->atom_sizeL ); - Flip4 ( &creator_atomP->creator_codeLu ); - Flip4 ( &creator_atomP->creator_eventLu ); + newCr8r->magic = MakeUns32LE ( 0xBEEFCAFE ); + newCr8r->size = MakeUns32LE ( sizeof(Cr8rBoxContent) ); + newCr8r->majorVer = MakeUns16LE ( 1 ); - XMP_Assert ( creator_atomP->magicLu == AdobeCreatorAtom_Magic ); -} + } else { -// ------------------------------------------------------------------------------------------------- + const Cr8rBoxContent * oldCr8r = (Cr8rBoxContent*) (*cr8rChunk)->oldValue.data(); + memcpy ( newCr8r, oldCr8r, sizeof(Cr8rBoxContent) ); + if ( GetUns32LE ( &newCr8r->magic ) != 0xBEEFCAFE ) { // Make sure we write LE numbers. + Flip4 ( &newCr8r->magic ); + Flip4 ( &newCr8r->size ); + Flip2 ( &newCr8r->majorVer ); + Flip2 ( &newCr8r->minorVer ); + Flip4 ( &newCr8r->creatorCode ); + Flip4 ( &newCr8r->appleEvent ); + } -static void CreatorAtom_ToBE ( CR8R_CreatorAtom * creator_atomP ) -{ - creator_atomP->atom_vers_majorS = MakeUns16BE ( creator_atomP->atom_vers_majorS ); - creator_atomP->atom_vers_minorS = MakeUns16BE ( creator_atomP->atom_vers_minorS ); + } + + if ( ! creatorCode.empty() ) { + newCr8r->creatorCode = MakeUns32LE ( (XMP_Uns32) strtoul ( creatorCode.c_str(), 0, 0 ) ); + } + + if ( ! appleEvent.empty() ) { + newCr8r->appleEvent = MakeUns32LE ( (XMP_Uns32) strtoul ( appleEvent.c_str(), 0, 0 ) ); + } + + if ( ! fileExt.empty() ) SetBufferedString ( newCr8r->fileExt, fileExt, sizeof ( newCr8r->fileExt ) ); + if ( ! appOptions.empty() ) SetBufferedString ( newCr8r->appOptions, appOptions, sizeof ( newCr8r->appOptions ) ); + if ( ! appName.empty() ) SetBufferedString ( newCr8r->appName, appName, sizeof ( newCr8r->appName ) ); - creator_atomP->magicLu = MakeUns32BE ( creator_atomP->magicLu ); - creator_atomP->atom_sizeL = MakeUns32BE ( creator_atomP->atom_sizeL ); - creator_atomP->creator_codeLu = MakeUns32BE ( creator_atomP->creator_codeLu ); - creator_atomP->creator_eventLu = MakeUns32BE ( creator_atomP->creator_eventLu ); } -// ------------------------------------------------------------------------------------------------- - -static void ProjectLinkAtom_MakeValid ( Embed_ProjectLinkAtom * link_atomP ) +static void exportXMPtoListChunk( XMP_Uns32 id, XMP_Uns32 containerType, + RIFF_MetaHandler* handler, ContainerChunk** listChunk, Mapping mapping[]) { - // If already valid, no conversion is needed. - if ( link_atomP->magicLu == PR_PROJECT_LINK_MAGIC ) return; - - // do the header - Flip4 ( &link_atomP->magicLu ); - Flip4 ( &link_atomP->atom_sizeL ); - Flip2 ( &link_atomP->atom_vers_apiS ); - Flip2 ( &link_atomP->atom_vers_codeS ); + // note: ContainerChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation) + SXMPMeta* xmp = &handler->xmpObj; + bool listChunkIsNeeded = false; // assume for now + + // ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte. + bool optionalNUL = (handler->parent->format == kXMP_WAVFile); - // do the FSSpec data - Flip2 ( &link_atomP->fullPath.vRefNum ); - Flip4 ( &link_atomP->fullPath.parID ); + for ( int p=0; mapping[p].chunkID != 0; ++p ) { // go through all potential property mappings - XMP_Assert ( link_atomP->magicLu == PR_PROJECT_LINK_MAGIC ); -} + bool propExists = false; + std::string value, actualLang; -// ------------------------------------------------------------------------------------------------- + switch ( mapping[p].propType ) { -static std::string CharsToString ( const char* buffer, int maxBuffer ) -{ - // convert possibly non-zero terminated char buffer to std::string - std::string result; + // get property. if existing, remove from XMP (to avoid redundant storage) + case prop_TIMEVALUE: + propExists = xmp->GetStructField ( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue", &value, 0 ); + break; - char bufferz[256]; - XMP_Assert ( maxBuffer < 256 ); - if ( maxBuffer >= 256 ) return result; + case prop_LOCALIZED_TEXT: + propExists = xmp->GetLocalizedText ( mapping[p].ns, mapping[p].prop, "", "x-default", &actualLang, &value, 0); + if ( actualLang != "x-default" ) propExists = false; // no "x-default" => nothing to reconcile ! + break; - memcpy ( bufferz, buffer, maxBuffer ); - bufferz[maxBuffer] = 0; + case prop_ARRAYITEM: + propExists = xmp->GetArrayItem ( mapping[p].ns, mapping[p].prop, 1, &value, 0 ); + break; - result = bufferz; - return result; + case prop_SIMPLE: + propExists = xmp->GetProperty ( mapping[p].ns, mapping[p].prop, &value, 0 ); + break; -} + default: + XMP_Throw ( "internal error", kXMPErr_InternalFailure ); -// ------------------------------------------------------------------------------------------------- + } -bool CreatorAtom::Import ( SXMPMeta& xmpObj, - LFA_FileRef fileRef, - RIFF_Support::RiffState& riffState ) -{ - static const long myProjectLink = MakeFourCC ( 'P','r','m','L' ); - - unsigned long projectLinkSize; - bool ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myProjectLink, 0, 0, 0, &projectLinkSize ); - if ( ok ) { + if ( ! propExists ) { - Embed_ProjectLinkAtom epla; + if ( *listChunk != 0 ) (*listChunk)->removeValue ( mapping[p].chunkID ); - std::string projectPathString; - RIFF_Support::GetRIFFChunk ( fileRef, riffState, myProjectLink, 0, 0, (char*) &epla, &projectLinkSize ); - if ( ok ) { - ProjectLinkAtom_MakeValid ( &epla ); - projectPathString = epla.fullPath.name; - } + } else { - if ( ! projectPathString.empty() ) { + listChunkIsNeeded = true; + if ( *listChunk == 0 ) *listChunk = new ContainerChunk ( handler->riffChunks[0], id, containerType ); - if ( projectPathString[0] == '/' ) { - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", - kXMP_NS_CreatorAtom, "posixProjectPath", projectPathString, 0 ); - } else if ( projectPathString.substr(0,4) == std::string("\\\\?\\") ) { - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", - kXMP_NS_CreatorAtom, "uncProjectPath", projectPathString, 0 ); - } + valueMap* cm = &(*listChunk)->childmap; + valueMapIter result = cm->find( mapping[p].chunkID ); + ValueChunk* propChunk = 0; - std::string projectTypeString; - switch ( epla.exportType ) { - case Embed_ExportTypeMovie : projectTypeString = "movie"; break; - case Embed_ExportTypeStill : projectTypeString = "still"; break; - case Embed_ExportTypeAudio : projectTypeString = "audio"; break; - case Embed_ExportTypeCustom : projectTypeString = "custom"; break; + if ( result != cm->end() ) { + propChunk = result->second; + } else { + propChunk = new ValueChunk ( *listChunk, std::string(), mapping[p].chunkID ); } - if ( ! projectTypeString.empty() ) { - xmpObj.SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", projectTypeString.c_str() ); - } + propChunk->SetValue ( value.c_str(), optionalNUL ); } - } - - unsigned long creatorAtomSize = 0; - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, 0, &creatorAtomSize ); - if ( ok ) { - - CR8R_CreatorAtom creatorAtom; - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, (char*) &creatorAtom, &creatorAtomSize ); + } // for each property - if ( ok ) { - - CreatorAtom_MakeValid ( &creatorAtom ); - - char buffer[256]; - std::string xmpString; - - sprintf ( buffer, "%d", creatorAtom.creator_codeLu ); - xmpString = buffer; - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", xmpString, 0 ); - - sprintf ( buffer, "%d", creatorAtom.creator_eventLu ); - xmpString = buffer; - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", xmpString, 0 ); + if ( (! listChunkIsNeeded) && (*listChunk != 0) && ((*listChunk)->children.size() == 0) ) { + (*listChunk)->parent->replaceChildWithJunk ( *listChunk ); + (*listChunk) = 0; // reset direct Chunk pointer + } - xmpString = CharsToString ( creatorAtom.creator_extAC, sizeof(creatorAtom.creator_extAC) ); - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", xmpString, 0 ); +} - xmpString = CharsToString ( creatorAtom.creator_flagAC, sizeof(creatorAtom.creator_flagAC) ); - xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", xmpString, 0 ); +void exportAndRemoveProperties ( RIFF_MetaHandler* handler ) +{ + SXMPMeta xmpObj = handler->xmpObj; - xmpString = CharsToString ( creatorAtom.creator_nameAC, sizeof(creatorAtom.creator_nameAC) ); - xmpObj.SetProperty ( kXMP_NS_XMP, "CreatorTool", xmpString, 0 ); - - } + exportXMPtoCr8rChunk ( handler, &handler->cr8rChunk ); + // 1/4 BWF Bext extension chunk ----------------------------------------------- + if ( handler->parent->format == kXMP_WAVFile ) { // applies only to WAV + exportXMPtoBextChunk ( handler, &handler->bextChunk ); } - return ok; - -} + // 2/4 DISP chunk + if ( handler->parent->format == kXMP_WAVFile ) { // create for WAVE only -// ------------------------------------------------------------------------------------------------- + std::string actualLang, xmpValue; + bool r = xmpObj.GetLocalizedText ( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, &xmpValue, 0 ); -// *** Not in C library: -#ifndef min - #define min(a,b) ( (a < b) ? a : b ) -#endif + if ( r && ( actualLang == "x-default" ) ) { // prop exists? -#define EnsureFinalNul(buffer) buffer [ sizeof(buffer) - 1 ] = 0 + // the 'right' DISP is lead by a 32 bit low endian 0x0001 + std::string dispValue = std::string( "\x1\0\0\0", 4 ); + dispValue.append ( xmpValue ); -bool CreatorAtom::Update ( SXMPMeta& xmpObj, - LFA_FileRef fileRef, - long riffType, - RIFF_Support::RiffState& riffState ) -{ + if ( handler->dispChunk == 0 ) { + handler->dispChunk = new RIFF::ValueChunk ( handler->riffChunks.at(0), std::string(), kChunk_DISP ); + } - // Creator Atom related values. - bool found = false; - std::string posixPathString, uncPathString; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "posixProjectPath", &posixPathString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "uncProjectPath", &uncPathString, 0 ) ) found = true; + // ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte. + handler->dispChunk->SetValue ( dispValue, ValueChunk::kNULisOptional ); - std::string applicationCodeString, invocationAppleEventString, extensionString, invocationFlagsString, creatorToolString; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &applicationCodeString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &invocationAppleEventString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &extensionString, 0 ) ) found = true; - if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &invocationFlagsString, 0 ) ) found = true; - if ( xmpObj.GetProperty ( kXMP_NS_XMP, "CreatorTool", &creatorToolString, 0 ) ) found = true; + } else { // remove Disp Chunk.. - // No Creator Atom information present. - if ( ! found ) return true; + if ( handler->dispChunk != 0 ) { // ..if existing + ContainerChunk* mainChunk = handler->riffChunks.at(0); + Chunk* needle = handler->dispChunk; + chunkVectIter iter = mainChunk->getChild ( needle ); + if ( iter != mainChunk->children.end() ) { + mainChunk->replaceChildWithJunk ( *iter ); + handler->dispChunk = 0; + mainChunk->hasChange = true; + } + } - // Read Legacy Creator Atom. - unsigned long creatorAtomSize = 0; - CR8R_CreatorAtom creatorAtomLegacy; - CreatorAtom_Initialize ( creatorAtomLegacy ); - bool ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, 0, &creatorAtomSize ); - if ( ok ) { - XMP_Assert ( creatorAtomSize == sizeof(CR8R_CreatorAtom) ); - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, (char*) &creatorAtomLegacy, &creatorAtomSize ); - CreatorAtom_MakeValid ( &creatorAtomLegacy ); - } + } - // Generate new Creator Atom from XMP. - CR8R_CreatorAtom creatorAtomViaXMP; - CreatorAtom_Initialize ( creatorAtomViaXMP ); - if ( ! applicationCodeString.empty() ) { - creatorAtomViaXMP.creator_codeLu = strtoul ( applicationCodeString.c_str(), 0, 0 ); - } - if ( ! invocationAppleEventString.empty() ) { - creatorAtomViaXMP.creator_eventLu = strtoul ( invocationAppleEventString.c_str(), 0, 0 ); - } - if ( ! extensionString.empty() ) { - strncpy ( creatorAtomViaXMP.creator_extAC, extensionString.c_str(), sizeof(creatorAtomViaXMP.creator_extAC) ); - EnsureFinalNul ( creatorAtomViaXMP.creator_extAC ); - } - if ( ! invocationFlagsString.empty() ) { - strncpy ( creatorAtomViaXMP.creator_flagAC, invocationFlagsString.c_str(), sizeof(creatorAtomViaXMP.creator_flagAC) ); - EnsureFinalNul ( creatorAtomViaXMP.creator_flagAC ); - } - if ( ! creatorToolString.empty() ) { - strncpy ( creatorAtomViaXMP.creator_nameAC, creatorToolString.c_str(), sizeof(creatorAtomViaXMP.creator_nameAC) ); - EnsureFinalNul ( creatorAtomViaXMP.creator_nameAC ); } - // Write new Creator Atom, if necessary. - if ( memcmp ( &creatorAtomViaXMP, &creatorAtomLegacy, sizeof(CR8R_CreatorAtom) ) != 0 ) { - CreatorAtom_ToBE ( &creatorAtomViaXMP ); - ok = RIFF_Support::PutChunk ( fileRef, riffState, riffType, myCreatorAtom, (char*)&creatorAtomViaXMP, sizeof(CR8R_CreatorAtom) ); - } + // 3/4 LIST:INFO + exportXMPtoListChunk ( kChunk_LIST, kType_INFO, handler, &handler->listInfoChunk, listInfoProps ); - return ok; + // 4/4 LIST:Tdat + exportXMPtoListChunk ( kChunk_LIST, kType_Tdat, handler, &handler->listTdatChunk, listTdatProps ); } + +} // namespace RIFF diff --git a/source/XMPFiles/FormatSupport/RIFF_Support.hpp b/source/XMPFiles/FormatSupport/RIFF_Support.hpp index 8065e38..a0b972b 100644 --- a/source/XMPFiles/FormatSupport/RIFF_Support.hpp +++ b/source/XMPFiles/FormatSupport/RIFF_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2009 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -11,183 +11,32 @@ // ================================================================================================= #include "XMP_Environment.h" // ! This must be the first include. - #include <vector> - #include "XMPFiles_Impl.hpp" -#define MakeFourCC(a,b,c,d) ((long)a | ((long)b << 8) | ((long)c << 16) | ((long)d << 24)) - -#if XMP_WinBuild - #include <vfw.h> -#else - #ifndef FOURCC_RIFF - #define FOURCC_RIFF MakeFourCC ('R', 'I', 'F', 'F') - #endif - #ifndef FOURCC_LIST - #define FOURCC_LIST MakeFourCC ('L', 'I', 'S', 'T') - #endif - #ifndef listtypeAVIMOVIE - #define listtypeAVIMOVIE MakeFourCC ('m', 'o', 'v', 'i') - #endif -#endif - -namespace RIFF_Support -{ - // Some types, if not already defined - #ifndef UInt64 - typedef unsigned long long UInt64; - #endif - #ifndef UInt32 - typedef unsigned long UInt32; - #endif +// ahead declaration: +class RIFF_MetaHandler; - /** - ** Obtain the meta-data for the tagID provided. - ** Returns true for success - */ - bool GetMetaData ( LFA_FileRef inFileRef, long tagID, char * outBuffer, unsigned long * outBufferSize ); +namespace RIFF { - /** - ** Write the meta-data for the tagID provided. - ** Returns true for success - */ - bool SetMetaData ( LFA_FileRef inFileRef, long riffType, long tagID, const char * inBuffer, unsigned long inBufferSize ); - - - - /** - ** A class to hold the information - ** about a particular chunk. - */ - class RiffTag { - public: + // declare ahead + class Chunk; + class ContainerChunk; + class ValueChunk; + class XMPChunk; - RiffTag() : pos(0), tagID(0), len(0), parent(0), parentID(0), subtypeID(0) {} - virtual ~RiffTag() {} - - UInt64 pos; /* file offset of chunk data */ - long tagID; /* ckid of chunk */ - UInt32 len; /* length of chunk data */ - long parent; /* chunk# of parent */ - long parentID; /* FOURCC of parent */ - long subtypeID; /* Subtype of the tag (aka LIST ID) */ - - }; - - typedef std::vector<RiffTag> RiffVector; - typedef RiffVector::iterator RiffIterator; - - /** - ** A class to hold a table of the parsed - ** chunks from a file. Its validity - ** expires when new chunks are added. - */ - class RiffState { - public: - - RiffState() : riffpos(0), rifflen(0), next(0) {} - virtual ~RiffState() {} - - UInt64 riffpos; /* file offset of current RIFF */ - long rifflen; /* length of RIFF incl. header */ - long next; /* next one to search */ - RiffVector tags; /* vector of chunks */ - - }; - - struct ltag { - long id; - UInt32 len; - long subid; - }; - - /** - ** Read from the RIFF file, and build a table of the chunks - ** in the RIFFState class provided. - ** Returns the number of chunks found. - */ - long OpenRIFF ( LFA_FileRef inFileRef, RiffState & inOutRiffState ); + /* This rountines imports the properties found into the + xmp packet. Use after parsing. */ + void importProperties( RIFF_MetaHandler* handler ); - /** - ** Get a chunk from an existing RIFFState, obtained from - ** a call to OpenRIFF. - ** If NULL is passed for the outBuffer, the outBufferSize parameter - ** will contain the field size if true if returned. - ** - ** Returns true if the chunk is found. - ** - ** position of chunk _contents_ is returned in postPtr - */ - bool GetRIFFChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long tagID, long parentID, - long subtypeID, char * outBuffer, unsigned long * outBufferSize, UInt64* posPtr = 0); + /* This rountines exports XMP properties to the respective Chunks, + creating those if needed. No writing to file here. */ + void exportAndRemoveProperties( RIFF_MetaHandler* handler ); + /* will relocated a wrongly placed chunk (one of XMP, LIST:Info, LIST:Tdat= + from RIFF::avix back to main chunk. Chunk itself not touched. */ + void relocateWronglyPlacedXMPChunk( RIFF_MetaHandler* handler ); - /** - ** The routine finds an existing list and tags it as Padding - ** - ** Returns true if success - */ - bool MarkChunkAsPadding ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, long tagID, long subtypeID ); - - - /** - ** The routine finds an existing location to put the chunk into if - ** available, otherwise it creates a new chunk and writes to it. - ** - ** Returns true if success - */ - bool PutChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, long tagID, const char * inBuffer, UInt32 inBufferSize ); - - /** - ** Locates the position of a chunk. - ** All parameters except the RiffState are optional. - ** - ** Return if found. - */ - bool FindChunk ( RiffState & inOutRiffState, long tagID, long parentID, long subtypeID, long * starttag, UInt32 * len, UInt64 * pos ); - - /** - ** Low level routine to write a chunk. - ** - ** Returns true if write succeeded. - */ - bool WriteChunk ( LFA_FileRef inFileRef, long tagID, const char * data, UInt32 len ); - - /** - ** Rewrites data into an existing chunk, not writing the header like WriteChunk - ** - ** Returns true if found and write succeeded. - */ - bool RewriteChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long tagID, long parentID, const char * inData ); - - /** - ** Attempts to find a location to write a chunk, and if not found, prepares a chunk - ** at the end of the file. - ** - ** Returns true if successful. - */ - bool MakeChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long riffType, UInt32 len ); - -} // namespace RIFF_Support - -// ================================================================================================= - -// *** Could be moved to a separate header - -namespace CreatorAtom { - - bool Import ( SXMPMeta& xmpObj, - LFA_FileRef fileRef, - RIFF_Support::RiffState& riffState ); - - bool Update ( SXMPMeta& xmpObj, - LFA_FileRef fileRef, - long riffType, - RIFF_Support::RiffState& riffState ); - -} - -// ================================================================================================= +} // namespace RIFF #endif // __RIFF_Support_hpp__ diff --git a/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp b/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp index a81e8af..bef1b11 100644 --- a/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp +++ b/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -32,11 +32,11 @@ static inline void NormalizeToCR ( std::string * value ) { char * strPtr = (char*) value->data(); char * strEnd = strPtr + value->size(); - + for ( ; strPtr < strEnd; ++strPtr ) { if ( *strPtr == kLF ) *strPtr = kCR; } - + } // NormalizeToCR // ================================================================================================= @@ -47,11 +47,11 @@ static inline void NormalizeToLF ( std::string * value ) { char * strPtr = (char*) value->data(); char * strEnd = strPtr + value->size(); - + for ( ; strPtr < strEnd; ++strPtr ) { if ( *strPtr == kCR ) *strPtr = kLF; } - + } // NormalizeToLF // ================================================================================================= @@ -60,70 +60,52 @@ static inline void NormalizeToLF ( std::string * value ) // // Compute a 128 bit (16 byte) MD5 digest of the full IPTC block. -static inline void ComputeIPTCDigest ( IPTC_Manager * iptc, MD5_Digest * digest ) +static inline void ComputeIPTCDigest ( const void * iptcPtr, const XMP_Uns32 iptcLen, MD5_Digest * digest ) { - MD5_CTX context; - void * iptcData; - XMP_Uns32 iptcLen; + MD5_CTX context; - iptcLen = iptc->UpdateMemoryDataSets ( &iptcData ); - MD5Init ( &context ); - MD5Update ( &context, (XMP_Uns8*)iptcData, iptcLen ); + MD5Update ( &context, (XMP_Uns8*)iptcPtr, iptcLen ); MD5Final ( *digest, &context ); } // ComputeIPTCDigest; // ================================================================================================= -// ReconcileUtils::CheckIPTCDigest +// PhotoDataUtils::CheckIPTCDigest // =============================== -int ReconcileUtils::CheckIPTCDigest ( IPTC_Manager * iptc, const PSIR_Manager & psir ) +int PhotoDataUtils::CheckIPTCDigest ( const void * newPtr, const XMP_Uns32 newLen, const void * oldDigest ) { MD5_Digest newDigest; - PSIR_Manager::ImgRsrcInfo ir1061; - - ComputeIPTCDigest ( iptc, &newDigest ); - bool found = psir.GetImgRsrc ( kPSIR_IPTCDigest, &ir1061 ); - - if ( ! found ) return kDigestMissing; - if ( ir1061.dataLen != 16 ) return kDigestMissing; - - if ( memcmp ( newDigest, ir1061.dataPtr, 16 ) == 0 ) return kDigestMatches; + ComputeIPTCDigest ( newPtr, newLen, &newDigest ); + if ( memcmp ( &newDigest, oldDigest, 16 ) == 0 ) return kDigestMatches; return kDigestDiffers; - -} // ReconcileUtils::CheckIPTCDigest + +} // PhotoDataUtils::CheckIPTCDigest // ================================================================================================= -// ReconcileUtils::SetIPTCDigest -// =============================== +// PhotoDataUtils::SetIPTCDigest +// ============================= -void ReconcileUtils::SetIPTCDigest ( IPTC_Manager * iptc, PSIR_Manager * psir ) +void PhotoDataUtils::SetIPTCDigest ( void * iptcPtr, XMP_Uns32 iptcLen, PSIR_Manager * psir ) { MD5_Digest newDigest; - - ComputeIPTCDigest ( iptc, &newDigest ); + + ComputeIPTCDigest ( iptcPtr, iptcLen, &newDigest ); psir->SetImgRsrc ( kPSIR_IPTCDigest, &newDigest, sizeof(newDigest) ); - -} // ReconcileUtils::SetIPTCDigest + +} // PhotoDataUtils::SetIPTCDigest // ================================================================================================= // ================================================================================================= // ================================================================================================= -// ImportIPTC_Simple -// ================= +// PhotoDataUtils::ImportIPTC_Simple +// ================================= -static void ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) +void PhotoDataUtils::ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) { - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; - } - std::string utf8Str; size_t count = iptc.GetDataSet_UTF8 ( id, &utf8Str ); @@ -132,59 +114,183 @@ static void ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp, int d xmp->SetProperty ( xmpNS, xmpProp, utf8Str.c_str() ); } -} // ImportIPTC_Simple +} // PhotoDataUtils::ImportIPTC_Simple // ================================================================================================= -// ImportIPTC_LangAlt -// ================== +// PhotoDataUtils::ImportIPTC_LangAlt +// ================================== -static void ImportIPTC_LangAlt ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) +void PhotoDataUtils::ImportIPTC_LangAlt ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) { - if ( digestState == kDigestDiffers ) { - std::string xdItemPath = xmpProp; // Delete just the x-default item, not the whole array. - xdItemPath += "[?xml:lang='x-default']"; - xmp->DeleteProperty ( xmpNS, xdItemPath.c_str() ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; // Check the entire array here. - } - std::string utf8Str; - size_t count = iptc.GetDataSet_UTF8 ( id, &utf8Str ); - + if ( count != 0 ) { NormalizeToLF ( &utf8Str ); xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", utf8Str.c_str() ); } -} // ImportIPTC_LangAlt +} // PhotoDataUtils::ImportIPTC_LangAlt // ================================================================================================= -// ImportIPTC_Array -// ================ +// PhotoDataUtils::ImportIPTC_Array +// ================================ -static void ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) +void PhotoDataUtils::ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) { - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; - } - std::string utf8Str; size_t count = iptc.GetDataSet ( id, 0 ); + xmp->DeleteProperty ( xmpNS, xmpProp ); + + XMP_OptionBits arrayForm = kXMP_PropArrayIsUnordered; + if ( XMP_LitMatch ( xmpNS, kXMP_NS_DC ) && XMP_LitMatch ( xmpProp, "creator" ) ) arrayForm = kXMP_PropArrayIsOrdered; + for ( size_t ds = 0; ds < count; ++ds ) { (void) iptc.GetDataSet_UTF8 ( id, &utf8Str, ds ); NormalizeToLF ( &utf8Str ); - xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsUnordered, utf8Str.c_str() ); + xmp->AppendArrayItem ( xmpNS, xmpProp, arrayForm, utf8Str.c_str() ); } -} // ImportIPTC_Array +} // PhotoDataUtils::ImportIPTC_Array + +// ================================================================================================= +// PhotoDataUtils::ImportIPTC_Date +// =============================== +// +// An IPTC (IIM) date is 8 characters, YYYYMMDD. Include the time portion if it is present. The IPTC +// time is HHMMSSxHHMM, where 'x' is '+' or '-'. Be tolerant of some ill-formed dates and times. +// Apparently some non-Adobe apps put strings like "YYYY-MM-DD" or "HH:MM:SSxHH:MM" in the IPTC. +// Allow a missing time zone portion. + +// *** The date/time handling differs from the MWG 1.0.1 policy, following a proposed tweak to MWG: +// *** Exif DateTimeOriginal <-> XMP exif:DateTimeOriginal +// *** IPTC DateCreated <-> XMP photoshop:DateCreated +// *** Exif DateTimeDigitized <-> IPTC DigitalCreateDate <-> XMP xmp:CreateDate + +void PhotoDataUtils::ImportIPTC_Date ( XMP_Uns8 dateID, const IPTC_Manager & iptc, SXMPMeta * xmp ) +{ + XMP_Uns8 timeID; + XMP_StringPtr xmpNS, xmpProp; + + if ( dateID == kIPTC_DateCreated ) { + timeID = kIPTC_TimeCreated; + xmpNS = kXMP_NS_Photoshop; + xmpProp = "DateCreated"; + } else if ( dateID == kIPTC_DigitalCreateDate ) { + timeID = kIPTC_DigitalCreateTime; + xmpNS = kXMP_NS_XMP; + xmpProp = "CreateDate"; + } else { + XMP_Throw ( "Unrecognized dateID", kXMPErr_BadParam ); + } + + // First gather the date portion. + + IPTC_Manager::DataSetInfo dsInfo; + size_t count = iptc.GetDataSet ( dateID, &dsInfo ); + if ( count == 0 ) return; + + size_t chPos, digits; + XMP_DateTime xmpDate; + memset ( &xmpDate, 0, sizeof(xmpDate) ); + + chPos = 0; + for ( digits = 0; digits < 4; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.year = (xmpDate.year * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + + if ( dsInfo.dataPtr[chPos] == '-' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.month = (xmpDate.month * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.month < 1 ) xmpDate.month = 1; + if ( xmpDate.month > 12 ) xmpDate.month = 12; + + if ( dsInfo.dataPtr[chPos] == '-' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.day = (xmpDate.day * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.day < 1 ) xmpDate.day = 1; + if ( xmpDate.day > 31 ) xmpDate.day = 28; // Close enough. + + if ( chPos != dsInfo.dataLen ) return; // The DataSet is ill-formed. + xmpDate.hasDate = true; + + // Now add the time portion if present. + + count = iptc.GetDataSet ( timeID, &dsInfo ); + if ( count != 0 ) { + + chPos = 0; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.hour = (xmpDate.hour * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.hour < 0 ) xmpDate.hour = 0; + if ( xmpDate.hour > 23 ) xmpDate.hour = 23; + + if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.minute = (xmpDate.minute * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.minute < 0 ) xmpDate.minute = 0; + if ( xmpDate.minute > 59 ) xmpDate.minute = 59; + + if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.second = (xmpDate.second * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.second < 0 ) xmpDate.second = 0; + if ( xmpDate.second > 59 ) xmpDate.second = 59; + + xmpDate.hasTime = true; + + if ( (dsInfo.dataPtr[chPos] != ' ') && (dsInfo.dataPtr[chPos] != 0) ) { // Tolerate a missing TZ. + + if ( dsInfo.dataPtr[chPos] == '+' ) { + xmpDate.tzSign = kXMP_TimeEastOfUTC; + } else if ( dsInfo.dataPtr[chPos] == '-' ) { + xmpDate.tzSign = kXMP_TimeWestOfUTC; + } else if ( chPos != dsInfo.dataLen ) { + return; // The DataSet is ill-formed. + } + + ++chPos; // Move past the time zone sign. + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.tzHour = (xmpDate.tzHour * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.tzHour < 0 ) xmpDate.tzHour = 0; + if ( xmpDate.tzHour > 23 ) xmpDate.tzHour = 23; + + if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.tzMinute = (xmpDate.tzMinute * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.tzMinute < 0 ) xmpDate.tzMinute = 0; + if ( xmpDate.tzMinute > 59 ) xmpDate.tzMinute = 59; + + if ( chPos != dsInfo.dataLen ) return; // The DataSet is ill-formed. + xmpDate.hasTimeZone = true; + + } + + } + + // Finally, set the XMP property. + + xmp->SetProperty_Date ( xmpNS, xmpProp, xmpDate ); + +} // PhotoDataUtils::ImportIPTC_Date // ================================================================================================= // ImportIPTC_IntellectualGenre @@ -195,24 +301,16 @@ static void ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp, int di // XMP and the number is dropped. Also, even though IIMv4.1 says that 2:04 is repeatable, the XMP // property to which it is mapped is simple. -static void ImportIPTC_IntellectualGenre ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - const char * xmpNS, const char * xmpProp ) +static void ImportIPTC_IntellectualGenre ( const IPTC_Manager & iptc, SXMPMeta * xmp ) { - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; - } - std::string utf8Str; size_t count = iptc.GetDataSet_UTF8 ( kIPTC_IntellectualGenre, &utf8Str ); if ( count == 0 ) return; NormalizeToLF ( &utf8Str ); - + XMP_StringPtr namePtr = utf8Str.c_str() + 4; - + if ( utf8Str.size() <= 4 ) { // No name in the IIM. Look up the number in our list of known genres. int i; @@ -224,7 +322,7 @@ static void ImportIPTC_IntellectualGenre ( const IPTC_Manager & iptc, SXMPMeta * namePtr = kIntellectualGenreMappings[i].name; } - xmp->SetProperty ( xmpNS, xmpProp, namePtr ); + xmp->SetProperty ( kXMP_NS_IPTCCore, "IntellectualGenre", namePtr ); } // ImportIPTC_IntellectualGenre @@ -237,19 +335,11 @@ static void ImportIPTC_IntellectualGenre ( const IPTC_Manager & iptc, SXMPMeta * // levels of the reference number hierarchy. The IPTC4XMP mapping rule is that only the reference // number is imported to XMP. -static void ImportIPTC_SubjectCode ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - const char * xmpNS, const char * xmpProp ) +static void ImportIPTC_SubjectCode ( const IPTC_Manager & iptc, SXMPMeta * xmp ) { - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; - } - std::string utf8Str; size_t count = iptc.GetDataSet_UTF8 ( kIPTC_SubjectCode, 0 ); - + for ( size_t ds = 0; ds < count; ++ds ) { (void) iptc.GetDataSet_UTF8 ( kIPTC_SubjectCode, &utf8Str, ds ); @@ -263,179 +353,92 @@ static void ImportIPTC_SubjectCode ( const IPTC_Manager & iptc, SXMPMeta * xmp, if ( (refNumEnd - refNumPtr) != 8 ) continue; // This DataSet is ill-formed. *refNumEnd = 0; // Ensure a terminating nul for the reference number portion. - xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsUnordered, refNumPtr ); + xmp->AppendArrayItem ( kXMP_NS_IPTCCore, "SubjectCode", kXMP_PropArrayIsUnordered, refNumPtr ); } } // ImportIPTC_SubjectCode // ================================================================================================= -// ImportIPTC_DateCreated -// ====================== -// -// An IPTC (IIM) date is 8 charcters YYYYMMDD. Include the time portion from 2:60 if it is present. -// The IPTC time is HHMMSSxHHMM, where 'x' is '+' or '-'. Be tolerant of some ill-formed dates and -// times. Apparently some non-Adobe apps put strings like "YYYY-MM-DD" or "HH:MM:SSxHH:MM" in the -// IPTC. Allow a missing time zone portion to mean UTC. +// PhotoDataUtils::Import2WayIPTC +// ============================== -static void ImportIPTC_DateCreated ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState, - const char * xmpNS, const char * xmpProp ) +void PhotoDataUtils::Import2WayIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ) { - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return; - } + if ( iptcDigestState == kDigestMatches ) return; // Ignore the IPTC if the digest matches. - // First gather the date portion. - - IPTC_Manager::DataSetInfo dsInfo; - size_t count = iptc.GetDataSet ( kIPTC_DateCreated, &dsInfo ); - if ( count == 0 ) return; - - size_t chPos, digits; - XMP_DateTime xmpDate; - memset ( &xmpDate, 0, sizeof(xmpDate) ); - - for ( chPos = 0, digits = 0; digits < 4; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.year = (xmpDate.year * 10) + (dsInfo.dataPtr[chPos] - '0'); + std::string oldStr, newStr; + IPTC_Writer oldIPTC; + + if ( iptcDigestState == kDigestDiffers ) { + PhotoDataUtils::ExportIPTC ( *xmp, &oldIPTC ); // Predict old IPTC DataSets based on the existing XMP. } - if ( dsInfo.dataPtr[chPos] == '-' ) ++chPos; - for ( digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.month = (xmpDate.month * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.month < 1 ) xmpDate.month = 1; - if ( xmpDate.month > 12 ) xmpDate.month = 12; + size_t newCount; + IPTC_Manager::DataSetInfo newInfo, oldInfo; - if ( dsInfo.dataPtr[chPos] == '-' ) ++chPos; - for ( digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.day = (xmpDate.day * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.day < 1 ) xmpDate.day = 1; - if ( xmpDate.day > 31 ) xmpDate.day = 28; // Close enough. - - if ( chPos != dsInfo.dataLen ) return; // The DataSet is ill-formed. + for ( size_t i = 0; kKnownDataSets[i].id != 255; ++i ) { - // Now add the time portion if present. - - count = iptc.GetDataSet ( kIPTC_TimeCreated, &dsInfo ); - if ( count != 0 ) { - - for ( chPos = 0, digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.hour = (xmpDate.hour * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.hour < 0 ) xmpDate.hour = 0; - if ( xmpDate.hour > 23 ) xmpDate.hour = 23; - - if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; - for ( digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.minute = (xmpDate.minute * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.minute < 0 ) xmpDate.minute = 0; - if ( xmpDate.minute > 59 ) xmpDate.minute = 59; + const DataSetCharacteristics & thisDS = kKnownDataSets[i]; + if ( thisDS.mapForm >= kIPTC_Map3Way ) continue; // The mapping is handled elsewhere, or not at all. - if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; - for ( digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.second = (xmpDate.second * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.second < 0 ) xmpDate.second = 0; - if ( xmpDate.second > 59 ) xmpDate.second = 59; + bool haveXMP = xmp->DoesPropertyExist ( thisDS.xmpNS, thisDS.xmpProp ); + newCount = PhotoDataUtils::GetNativeInfo ( iptc, thisDS.id, iptcDigestState, haveXMP, &newInfo ); + if ( newCount == 0 ) continue; // GetNativeInfo returns 0 for ignored local text. - if ( dsInfo.dataPtr[chPos] == '+' ) { - xmpDate.tzSign = kXMP_TimeEastOfUTC; - } else if ( dsInfo.dataPtr[chPos] == '-' ) { - xmpDate.tzSign = kXMP_TimeWestOfUTC; - } else if ( chPos != dsInfo.dataLen ) { - return; // The DataSet is ill-formed. + if ( iptcDigestState == kDigestMissing ) { + if ( haveXMP ) continue; // Keep the existing XMP. + } else if ( ! PhotoDataUtils::IsValueDifferent ( iptc, oldIPTC, thisDS.id ) ) { + continue; // Don't import values that match the previous export. } - ++chPos; // Move past the time zone sign. - for ( chPos = 0, digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.tzHour = (xmpDate.tzHour * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.tzHour < 0 ) xmpDate.tzHour = 0; - if ( xmpDate.tzHour > 23 ) xmpDate.tzHour = 23; + // The IPTC wins. Delete any existing XMP and import the DataSet. - if ( dsInfo.dataPtr[chPos] == ':' ) ++chPos; - for ( digits = 0; digits < 2; ++digits, ++chPos ) { - if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; - xmpDate.tzMinute = (xmpDate.tzMinute * 10) + (dsInfo.dataPtr[chPos] - '0'); - } - if ( xmpDate.tzMinute < 0 ) xmpDate.tzMinute = 0; - if ( xmpDate.tzMinute > 59 ) xmpDate.tzMinute = 59; - - if ( chPos != dsInfo.dataLen ) return; // The DataSet is ill-formed. - - } - - // Finally, set the XMP property. - - xmp->SetProperty_Date ( xmpNS, xmpProp, xmpDate ); - -} // ImportIPTC_DateCreated - -// ================================================================================================= -// ReconcileUtils::ImportIPTC -// ========================== - -void ReconcileUtils::ImportIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState ) -{ - if ( digestState == kDigestMatches ) return; - - for ( size_t i = 0; kKnownDataSets[i].id != 255; ++i ) { - - const DataSetCharacteristics & thisDS = kKnownDataSets[i]; + xmp->DeleteProperty ( thisDS.xmpNS, thisDS.xmpProp ); try { // Don't let errors with one stop the others. - + switch ( thisDS.mapForm ) { - + case kIPTC_MapSimple : - ImportIPTC_Simple ( iptc, xmp, digestState, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); + ImportIPTC_Simple ( iptc, xmp, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); break; - + case kIPTC_MapLangAlt : - ImportIPTC_LangAlt ( iptc, xmp, digestState, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); + ImportIPTC_LangAlt ( iptc, xmp, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); break; - + case kIPTC_MapArray : - ImportIPTC_Array ( iptc, xmp, digestState, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); + ImportIPTC_Array ( iptc, xmp, thisDS.id, thisDS.xmpNS, thisDS.xmpProp ); break; - + case kIPTC_MapSpecial : - if ( thisDS.id == kIPTC_IntellectualGenre ) { - ImportIPTC_IntellectualGenre ( iptc, xmp, digestState, thisDS.xmpNS, thisDS.xmpProp ); + if ( thisDS.id == kIPTC_DateCreated ) { + PhotoDataUtils::ImportIPTC_Date ( thisDS.id, iptc, xmp ); + } else if ( thisDS.id == kIPTC_IntellectualGenre ) { + ImportIPTC_IntellectualGenre ( iptc, xmp ); } else if ( thisDS.id == kIPTC_SubjectCode ) { - ImportIPTC_SubjectCode ( iptc, xmp, digestState, thisDS.xmpNS, thisDS.xmpProp ); - } else if ( thisDS.id == kIPTC_DateCreated ) { - ImportIPTC_DateCreated ( iptc, xmp, digestState, thisDS.xmpNS, thisDS.xmpProp ); - } + ImportIPTC_SubjectCode ( iptc, xmp ); + } else { + XMP_Assert ( false ); // Catch mapping errors. + } break; - + } } catch ( ... ) { - + // Do nothing, let other imports proceed. // ? Notify client? - + } } - -} // ReconcileUtils::ImportIPTC; + +} // PhotoDataUtils::Import2WayIPTC // ================================================================================================= -// ReconcileUtils::ImportPSIR +// PhotoDataUtils::ImportPSIR // ========================== // // There are only 2 standalone Photoshop image resources for XMP properties: @@ -446,21 +449,13 @@ void ReconcileUtils::ImportIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int // ! yes/don't-know model when importing. A missing or 0 value for PSIR 1034 cause xmpRights:Marked // ! to be deleted. -// **** What about 1008 and 1020? - -void ReconcileUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int digestState ) +void PhotoDataUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int iptcDigestState ) { PSIR_Manager::ImgRsrcInfo rsrcInfo; bool import; - - if ( digestState == kDigestMatches ) return; - - if ( digestState == kDigestDiffers ) { - // Delete the mapped XMP. This forces replacement and catches legacy deletions. - xmp->DeleteProperty ( kXMP_NS_XMP_Rights, "Marked" ); - xmp->DeleteProperty ( kXMP_NS_XMP_Rights, "WebStatement" ); - } - + + if ( iptcDigestState == kDigestMatches ) return; + try { // Don't let errors with one stop the others. import = psir.GetImgRsrc ( kPSIR_CopyrightFlag, &rsrcInfo ); if ( import ) import = (! xmp->DoesPropertyExist ( kXMP_NS_XMP_Rights, "Marked" )); @@ -471,29 +466,27 @@ void ReconcileUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int // Do nothing, let other imports proceed. // ? Notify client? } - + try { // Don't let errors with one stop the others. import = psir.GetImgRsrc ( kPSIR_CopyrightURL, &rsrcInfo ); if ( import ) import = (! xmp->DoesPropertyExist ( kXMP_NS_XMP_Rights, "WebStatement" )); if ( import ) { - #if ! XMP_UNIXBuild - std::string utf8; + std::string utf8; + if ( ReconcileUtils::IsUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen ) ) { + utf8.assign ( (char*)rsrcInfo.dataPtr, rsrcInfo.dataLen ); + } else if ( ! ignoreLocalText ) { ReconcileUtils::LocalToUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen, &utf8 ); - xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", utf8.c_str() ); - #else - // ! Hack until legacy-as-local issues are resolved for generic UNIX. - if ( ReconcileUtils::IsUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen ) ) { - std::string utf8 ( (char*)rsrcInfo.dataPtr, rsrcInfo.dataLen ); - xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", utf8.c_str() ); - } - #endif + } else { + import = false; // Inhibit the SetProperty call. + } + if ( import ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", utf8.c_str() ); } } catch ( ... ) { // Do nothing, let other imports proceed. // ? Notify client? } - -} // ReconcileUtils::ImportPSIR; + +} // PhotoDataUtils::ImportPSIR; // ================================================================================================= // ================================================================================================= @@ -502,22 +495,22 @@ void ReconcileUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int // ExportIPTC_Simple // ================= -static void ExportIPTC_Simple ( SXMPMeta * xmp, IPTC_Manager * iptc, +static void ExportIPTC_Simple ( const SXMPMeta & xmp, IPTC_Manager * iptc, const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) { std::string value; XMP_OptionBits xmpFlags; - bool found = xmp->GetProperty ( xmpNS, xmpProp, &value, &xmpFlags ); + bool found = xmp.GetProperty ( xmpNS, xmpProp, &value, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; } - + if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? - + NormalizeToCR ( &value ); - + size_t iptcCount = iptc->GetDataSet ( id, 0 ); if ( iptcCount > 1 ) iptc->DeleteDataSet ( id ); @@ -529,21 +522,21 @@ static void ExportIPTC_Simple ( SXMPMeta * xmp, IPTC_Manager * iptc, // ExportIPTC_LangAlt // ================== -static void ExportIPTC_LangAlt ( SXMPMeta * xmp, IPTC_Manager * iptc, +static void ExportIPTC_LangAlt ( const SXMPMeta & xmp, IPTC_Manager * iptc, const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) { std::string value; XMP_OptionBits xmpFlags; - bool found = xmp->GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + bool found = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; } if ( ! XMP_ArrayIsAltText ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? - - found = xmp->GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &value, 0 ); + + found = xmp.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &value, 0 ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; @@ -566,13 +559,13 @@ static void ExportIPTC_LangAlt ( SXMPMeta * xmp, IPTC_Manager * iptc, // XMP and IPTC array sizes differ, delete the entire IPTC and append all new values. If they match, // set the individual values in order - which lets SetDataSet apply its no-change optimization. -static void ExportIPTC_Array ( SXMPMeta * xmp, IPTC_Manager * iptc, +static void ExportIPTC_Array ( const SXMPMeta & xmp, IPTC_Manager * iptc, const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) { std::string value; XMP_OptionBits xmpFlags; - bool found = xmp->GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + bool found = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( id ); return; @@ -580,14 +573,14 @@ static void ExportIPTC_Array ( SXMPMeta * xmp, IPTC_Manager * iptc, if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? - XMP_Index xmpCount = xmp->CountArrayItems ( xmpNS, xmpProp ); + XMP_Index xmpCount = xmp.CountArrayItems ( xmpNS, xmpProp ); XMP_Index iptcCount = (XMP_Index) iptc->GetDataSet ( id, 0 ); - + if ( xmpCount != iptcCount ) iptc->DeleteDataSet ( id ); for ( XMP_Index ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0. - (void) xmp->GetArrayItem ( xmpNS, xmpProp, ds+1, &value, &xmpFlags ); + (void) xmp.GetArrayItem ( xmpNS, xmpProp, ds+1, &value, &xmpFlags ); if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? NormalizeToCR ( &value ); @@ -607,20 +600,19 @@ static void ExportIPTC_Array ( SXMPMeta * xmp, IPTC_Manager * iptc, // number is dropped. Also, even though IIMv4.1 says that 2:04 is repeatable, the XMP property to // which it is mapped is simple. Look up the XMP value in a list of known genres to get the number. -static void ExportIPTC_IntellectualGenre ( SXMPMeta * xmp, IPTC_Manager * iptc, - const char * xmpNS, const char * xmpProp ) +static void ExportIPTC_IntellectualGenre ( const SXMPMeta & xmp, IPTC_Manager * iptc ) { std::string xmpValue; XMP_OptionBits xmpFlags; - bool found = xmp->GetProperty ( xmpNS, xmpProp, &xmpValue, &xmpFlags ); + bool found = xmp.GetProperty ( kXMP_NS_IPTCCore, "IntellectualGenre", &xmpValue, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( kIPTC_IntellectualGenre ); return; } - + if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? - + NormalizeToCR ( &xmpValue ); int i; @@ -629,11 +621,11 @@ static void ExportIPTC_IntellectualGenre ( SXMPMeta * xmp, IPTC_Manager * iptc, if ( strcmp ( namePtr, kIntellectualGenreMappings[i].name ) == 0 ) break; } if ( kIntellectualGenreMappings[i].name == 0 ) return; // Not a known genre, don't export it. - + std::string iimValue = kIntellectualGenreMappings[i].refNum; iimValue += ':'; iimValue += xmpValue; - + size_t iptcCount = iptc->GetDataSet ( kIPTC_IntellectualGenre, 0 ); if ( iptcCount > 1 ) iptc->DeleteDataSet ( kIPTC_IntellectualGenre ); @@ -650,13 +642,12 @@ static void ExportIPTC_IntellectualGenre ( SXMPMeta * xmp, IPTC_Manager * iptc, // levels of the reference number hierarchy. The IPTC4XMP mapping rule is that only the reference // number is imported to XMP. We export with a fixed provider of "IPTC" and no optional names. -static void ExportIPTC_SubjectCode ( SXMPMeta * xmp, IPTC_Manager * iptc, - const char * xmpNS, const char * xmpProp ) +static void ExportIPTC_SubjectCode ( const SXMPMeta & xmp, IPTC_Manager * iptc ) { std::string xmpValue, iimValue; XMP_OptionBits xmpFlags; - bool found = xmp->GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + bool found = xmp.GetProperty ( kXMP_NS_IPTCCore, "SubjectCode", 0, &xmpFlags ); if ( ! found ) { iptc->DeleteDataSet ( kIPTC_SubjectCode ); return; @@ -664,14 +655,14 @@ static void ExportIPTC_SubjectCode ( SXMPMeta * xmp, IPTC_Manager * iptc, if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? - XMP_Index xmpCount = xmp->CountArrayItems ( xmpNS, xmpProp ); + XMP_Index xmpCount = xmp.CountArrayItems ( kXMP_NS_IPTCCore, "SubjectCode" ); XMP_Index iptcCount = (XMP_Index) iptc->GetDataSet ( kIPTC_SubjectCode, 0 ); - + if ( xmpCount != iptcCount ) iptc->DeleteDataSet ( kIPTC_SubjectCode ); for ( XMP_Index ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0. - (void) xmp->GetArrayItem ( xmpNS, xmpProp, ds+1, &xmpValue, &xmpFlags ); + (void) xmp.GetArrayItem ( kXMP_NS_IPTCCore, "SubjectCode", ds+1, &xmpValue, &xmpFlags ); if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? if ( xmpValue.size() != 8 ) continue; // ? Complain? @@ -686,119 +677,136 @@ static void ExportIPTC_SubjectCode ( SXMPMeta * xmp, IPTC_Manager * iptc, } // ExportIPTC_SubjectCode // ================================================================================================= -// ExportIPTC_DateCreated -// ====================== +// ExportIPTC_Date +// =============== // // The IPTC date and time are "YYYYMMDD" and "HHMMSSxHHMM" where 'x' is '+' or '-'. Export the IPTC // time only if already present, or if the XMP has a time portion. -static void ExportIPTC_DateCreated ( SXMPMeta * xmp, IPTC_Manager * iptc, - const char * xmpNS, const char * xmpProp ) -{ - std::string xmpStr; - XMP_DateTime xmpValue; - XMP_OptionBits xmpFlags; +// *** The date/time handling differs from the MWG 1.0 policy, following a proposed tweak to MWG: +// *** Exif DateTimeOriginal <-> IPTC DateCreated <-> XMP photoshop:DateCreated +// *** Exif DateTimeDigitized <-> IPTC DigitalCreateDate <-> XMP xmp:CreateDate - bool xmpHasTime = false; - - bool found = xmp->GetProperty ( xmpNS, xmpProp, &xmpStr, &xmpFlags ); - if ( found ) { - SXMPUtils::ConvertToDate ( xmpStr.c_str(), &xmpValue ); - if ( xmpStr.size() > 10 ) xmpHasTime = true; // Date-only values are up to "YYYY-MM-DD". +static void ExportIPTC_Date ( XMP_Uns8 dateID, const SXMPMeta & xmp, IPTC_Manager * iptc ) +{ + XMP_Uns8 timeID; + XMP_StringPtr xmpNS, xmpProp; + + if ( dateID == kIPTC_DateCreated ) { + timeID = kIPTC_TimeCreated; + xmpNS = kXMP_NS_Photoshop; + xmpProp = "DateCreated"; + } else if ( dateID == kIPTC_DigitalCreateDate ) { + timeID = kIPTC_DigitalCreateTime; + xmpNS = kXMP_NS_XMP; + xmpProp = "CreateDate"; } else { - iptc->DeleteDataSet ( kIPTC_DateCreated ); - iptc->DeleteDataSet ( kIPTC_TimeCreated ); - return; + XMP_Throw ( "Unrecognized dateID", kXMPErr_BadParam ); } - char iimValue[16]; - - // Set the IIM date portion. + iptc->DeleteDataSet ( dateID ); // ! Either the XMP does not exist and we want to + iptc->DeleteDataSet ( timeID ); // ! delete the IPTC, or we're replacing the IPTC. + + XMP_DateTime xmpValue; + bool found = xmp.GetProperty_Date ( xmpNS, xmpProp, &xmpValue, 0 ); + if ( ! found ) return; + + char iimValue[16]; // AUDIT: Big enough for "YYYYMMDD" (8) and "HHMMSS+HHMM" (11). + + // Set the IIM date portion as YYYYMMDD with zeroes for unknown parts. - snprintf ( iimValue, sizeof(iimValue), "%.4d%.2d%.2d", // AUDIT: Use of sizeof(iimValue) is safe. + snprintf ( iimValue, sizeof(iimValue), "%04d%02d%02d", // AUDIT: Use of sizeof(iimValue) is safe. xmpValue.year, xmpValue.month, xmpValue.day ); - if ( iimValue[8] != 0 ) return; // ? Complain? Delete the DataSet? - - size_t iptcCount = iptc->GetDataSet ( kIPTC_DateCreated, 0 ); - if ( iptcCount > 1 ) iptc->DeleteDataSet ( kIPTC_DateCreated ); - iptc->SetDataSet_UTF8 ( kIPTC_DateCreated, iimValue, 8, 0 ); // ! Don't append a 2nd DataSet! - - // Set the IIM time portion. + iptc->SetDataSet_UTF8 ( dateID, iimValue, 8 ); - iptcCount = iptc->GetDataSet ( kIPTC_TimeCreated, 0 ); - - if ( (iptcCount > 0) || xmpHasTime ) { - - snprintf ( iimValue, sizeof(iimValue), "%.2d%.2d%.2d%c%.2d%.2d", // AUDIT: Use of sizeof(iimValue) is safe. + // Set the IIM time portion as HHMMSS+HHMM (or -HHMM). Allow a missing time zone. + + if ( xmpValue.hasTimeZone ) { + snprintf ( iimValue, sizeof(iimValue), "%02d%02d%02d%c%02d%02d", // AUDIT: Use of sizeof(iimValue) is safe. xmpValue.hour, xmpValue.minute, xmpValue.second, ((xmpValue.tzSign == kXMP_TimeWestOfUTC) ? '-' : '+'), xmpValue.tzHour, xmpValue.tzMinute ); - if ( iimValue[11] != 0 ) return; // ? Complain? Delete the DataSet? - - if ( iptcCount > 1 ) iptc->DeleteDataSet ( kIPTC_TimeCreated ); - - iptc->SetDataSet_UTF8 ( kIPTC_TimeCreated, iimValue, 11, 0 ); // ! Don't append a 2nd DataSet! - + iptc->SetDataSet_UTF8 ( timeID, iimValue, 11 ); + } else if ( xmpValue.hasTime ) { + snprintf ( iimValue, sizeof(iimValue), "%02d%02d%02d", // AUDIT: Use of sizeof(iimValue) is safe. + xmpValue.hour, xmpValue.minute, xmpValue.second ); + iptc->SetDataSet_UTF8 ( timeID, iimValue, 6 ); + } else { + iptc->DeleteDataSet ( timeID ); } -} // ExportIPTC_DateCreated +} // ExportIPTC_Date // ================================================================================================= -// ReconcileUtils::ExportIPTC +// PhotoDataUtils::ExportIPTC // ========================== -void ReconcileUtils::ExportIPTC ( SXMPMeta * xmp, IPTC_Manager * iptc ) +void PhotoDataUtils::ExportIPTC ( const SXMPMeta & xmp, IPTC_Manager * iptc ) { - #if XMP_UNIXBuild - return; // ! Hack until the legacy-as-local issues are resolved for generic UNIX. - #endif - for ( size_t i = 0; kKnownDataSets[i].id != 255; ++i ) { - + try { // Don't let errors with one stop the others. - + const DataSetCharacteristics & thisDS = kKnownDataSets[i]; - + if ( thisDS.mapForm >= kIPTC_UnmappedText ) continue; + switch ( thisDS.mapForm ) { - + case kIPTC_MapSimple : ExportIPTC_Simple ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.id ); break; - + case kIPTC_MapLangAlt : ExportIPTC_LangAlt ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.id ); break; - + case kIPTC_MapArray : ExportIPTC_Array ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.id ); break; - + case kIPTC_MapSpecial : - if ( thisDS.id == kIPTC_IntellectualGenre ) { - ExportIPTC_IntellectualGenre ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp ); + if ( thisDS.id == kIPTC_DateCreated ) { + ExportIPTC_Date ( thisDS.id, xmp, iptc ); + } else if ( thisDS.id == kIPTC_IntellectualGenre ) { + ExportIPTC_IntellectualGenre ( xmp, iptc ); } else if ( thisDS.id == kIPTC_SubjectCode ) { - ExportIPTC_SubjectCode ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp ); - } else if ( thisDS.id == kIPTC_DateCreated ) { - ExportIPTC_DateCreated ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp ); - } + ExportIPTC_SubjectCode ( xmp, iptc ); + } else { + XMP_Assert ( false ); // Catch mapping errors. + } break; - + + case kIPTC_Map3Way : // The 3 way case is special for import, not for export. + if ( thisDS.id == kIPTC_DigitalCreateDate ) { + // ! Special case: Don't create IIM DigitalCreateDate. This can avoid PSD + // ! full rewrite due to new mapping from xmp:CreateDate. + if ( iptc->GetDataSet ( thisDS.id, 0 ) > 0 ) ExportIPTC_Date ( thisDS.id, xmp, iptc ); + } else if ( thisDS.id == kIPTC_Creator ) { + ExportIPTC_Array ( xmp, iptc, kXMP_NS_DC, "creator", kIPTC_Creator ); + } else if ( thisDS.id == kIPTC_CopyrightNotice ) { + ExportIPTC_LangAlt ( xmp, iptc, kXMP_NS_DC, "rights", kIPTC_CopyrightNotice ); + } else if ( thisDS.id == kIPTC_Description ) { + ExportIPTC_LangAlt ( xmp, iptc, kXMP_NS_DC, "description", kIPTC_Description ); + } else { + XMP_Assert ( false ); // Catch mapping errors. + } + } } catch ( ... ) { - + // Do nothing, let other exports proceed. // ? Notify client? - + } } - -} // ReconcileUtils::ExportIPTC; + +} // PhotoDataUtils::ExportIPTC; // ================================================================================================= -// ReconcileUtils::ExportPSIR +// PhotoDataUtils::ExportPSIR // ========================== // // There are only 2 standalone Photoshop image resources for XMP properties: @@ -811,11 +819,11 @@ void ReconcileUtils::ExportIPTC ( SXMPMeta * xmp, IPTC_Manager * iptc ) // ! We don't bother with the CR<->LF normalization for xmpRights:WebStatement. Very little chance // ! of having a raw CR character in a URI. -void ReconcileUtils::ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ) +void PhotoDataUtils::ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ) { bool found; std::string utf8Value; - + try { // Don't let errors with one stop the others. bool copyrighted = false; found = xmp.GetProperty ( kXMP_NS_XMP_Rights, "Marked", &utf8Value, 0 ); @@ -825,24 +833,23 @@ void ReconcileUtils::ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ) // Do nothing, let other exports proceed. // ? Notify client? } - + try { // Don't let errors with one stop the others. found = xmp.GetProperty ( kXMP_NS_XMP_Rights, "WebStatement", &utf8Value, 0 ); if ( ! found ) { psir->DeleteImgRsrc ( kPSIR_CopyrightURL ); + } else if ( ! ignoreLocalText ) { + std::string localValue; + ReconcileUtils::UTF8ToLocal ( utf8Value.c_str(), utf8Value.size(), &localValue ); + psir->SetImgRsrc ( kPSIR_CopyrightURL, localValue.c_str(), (XMP_Uns32)localValue.size() ); + } else if ( ReconcileUtils::IsASCII ( utf8Value.c_str(), utf8Value.size() ) ) { + psir->SetImgRsrc ( kPSIR_CopyrightURL, utf8Value.c_str(), (XMP_Uns32)utf8Value.size() ); } else { - #if ! XMP_UNIXBuild - std::string localValue; - ReconcileUtils::UTF8ToLocal ( utf8Value.c_str(), utf8Value.size(), &localValue ); - psir->SetImgRsrc ( kPSIR_CopyrightURL, localValue.c_str(), (XMP_Uns32)localValue.size() ); - #else - // ! Hack until legacy-as-local issues are resolved for generic UNIX. - psir->DeleteImgRsrc ( kPSIR_CopyrightURL ); - #endif + psir->DeleteImgRsrc ( kPSIR_CopyrightURL ); } } catch ( ... ) { // Do nothing, let other exports proceed. // ? Notify client? } -} // ReconcileUtils::ExportPSIR; +} // PhotoDataUtils::ExportPSIR; diff --git a/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp b/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp index 7c9f1f4..78eeaa4 100644 --- a/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp +++ b/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -14,201 +14,172 @@ // ================================================================================================= /// \file ReconcileLegacy.cpp -/// \brief Top level parts of utilities to reconcile between XMP and legacy metadata forms such as +/// \brief Top level parts of utilities to reconcile between XMP and legacy metadata forms such as /// TIFF/Exif and IPTC. /// // ================================================================================================= // ================================================================================================= -// ImportJTPtoXMP -// ============== +// ImportPhotoData +// =============== // // Import legacy metadata for JPEG, TIFF, and Photoshop files into the XMP. The caller must have // already done the file specific processing to select the appropriate sources of the TIFF stream, // the Photoshop image resources, and the IPTC. -// ! Note that kLegacyJTP_None does not literally mean no legacy. It means no IPTC-like legacy, i.e. -// ! stuff that Photoshop pre-7 would reconcile into the IPTC and thus affect the import order below. - -void ImportJTPtoXMP ( XMP_FileFormat srcFormat, - RecJTP_LegacyPriority lastLegacy, - TIFF_Manager * tiff, // ! Need for UserComment and RelatedSoundFile hack. - const PSIR_Manager & psir, - IPTC_Manager * iptc, // ! Need to call UpdateDataSets. - SXMPMeta * xmp, - XMP_OptionBits options /* = 0 */ ) +#define SaveExifTag(ns,prop) \ + if ( xmp->DoesPropertyExist ( ns, prop ) ) SXMPUtils::DuplicateSubtree ( *xmp, &savedExif, ns, prop ) +#define RestoreExifTag(ns,prop) \ + if ( savedExif.DoesPropertyExist ( ns, prop ) ) SXMPUtils::DuplicateSubtree ( savedExif, xmp, ns, prop ) + +void ImportPhotoData ( const TIFF_Manager & exif, + const IPTC_Manager & iptc, + const PSIR_Manager & psir, + int iptcDigestState, + SXMPMeta * xmp, + XMP_OptionBits options /* = 0 */ ) { bool haveXMP = XMP_OptionIsSet ( options, k2XMP_FileHadXMP ); - bool haveIPTC = XMP_OptionIsSet ( options, k2XMP_FileHadIPTC ); bool haveExif = XMP_OptionIsSet ( options, k2XMP_FileHadExif ); + bool haveIPTC = XMP_OptionIsSet ( options, k2XMP_FileHadIPTC ); - int iptcDigestState = kDigestMatches; // Default is to do no imports. - int tiffDigestState = kDigestMatches; - int exifDigestState = kDigestMatches; - - if ( ! haveXMP ) { + // Save some new Exif writebacks that can be XMP-only from older versions, delete all of the + // XMP's tiff: and exif: namespaces (they should only reflect native Exif), then put back the + // saved writebacks (which might get replaced by the native Exif values in the Import calls). + // The value of exif:ISOSpeedRatings is saved for special case handling of ISO over 65535. - // If there is no XMP then what we have differs. - if ( haveIPTC) iptcDigestState = kDigestDiffers; - if ( haveExif ) tiffDigestState = exifDigestState = kDigestDiffers; + SXMPMeta savedExif; - } else { - - // If there is XMP then check the digests for what we have. No legacy at all means the XMP - // is OK, and the CheckXyzDigest routines return true when there is no digest. This matches - // Photoshop, and avoids importing when an app adds XMP but does not export to the legacy or - // write a digest. - - if ( haveIPTC ) iptcDigestState = ReconcileUtils::CheckIPTCDigest ( iptc, psir ); - if ( iptcDigestState == kDigestMissing ) { - // *** Temporary hack to approximate Photoshop's behavior. Need fully documented policies! - tiffDigestState = exifDigestState = kDigestMissing; - } else if ( haveExif ) { - tiffDigestState = ReconcileUtils::CheckTIFFDigest ( *tiff, *xmp ); - exifDigestState = ReconcileUtils::CheckExifDigest ( *tiff, *xmp ); // ! Yes, the Exif is in the TIFF stream. - } - - } + SaveExifTag ( kXMP_NS_EXIF, "DateTimeOriginal" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSLatitude" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSLongitude" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSTimeStamp" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSAltitude" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSAltitudeRef" ); + SaveExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); - if ( lastLegacy > kLegacyJTP_TIFF_IPTC ) { - XMP_Throw ( "Invalid JTP legacy priority", kXMPErr_InternalFailure ); - } + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_TIFF, 0, kXMPUtil_DoAllProperties ); + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_EXIF, 0, kXMPUtil_DoAllProperties ); + + RestoreExifTag ( kXMP_NS_EXIF, "DateTimeOriginal" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSLatitude" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSLongitude" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSTimeStamp" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSAltitude" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSAltitudeRef" ); + RestoreExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); - // A TIFF file with tags 270, 315, or 33432 is currently the only case where the IPTC is less - // important than the TIFF/Exif. If there is no IPTC or no TIFF/Exif then the order does not - // matter. The order only affects collisions between those 3 TIFF tags and their IPTC counterparts. + // Not obvious here, but the logic in PhotoDataUtils follows the MWG reader guidelines. - if ( lastLegacy == kLegacyJTP_TIFF_TIFF_Tags ) { + PhotoDataUtils::ImportPSIR ( psir, xmp, iptcDigestState ); - if ( iptcDigestState != kDigestMatches ) { - ReconcileUtils::ImportIPTC ( *iptc, xmp, iptcDigestState ); - ReconcileUtils::ImportPSIR ( psir, xmp, iptcDigestState ); - } - if ( tiffDigestState != kDigestMatches ) ReconcileUtils::ImportTIFF ( *tiff, xmp, tiffDigestState, srcFormat ); - if ( exifDigestState != kDigestMatches ) ReconcileUtils::ImportExif ( *tiff, xmp, exifDigestState ); + if ( haveIPTC ) PhotoDataUtils::Import2WayIPTC ( iptc, xmp, iptcDigestState ); + if ( haveExif ) PhotoDataUtils::Import2WayExif ( exif, xmp, iptcDigestState ); - } else { + if ( haveExif | haveIPTC ) PhotoDataUtils::Import3WayItems ( exif, iptc, xmp, iptcDigestState ); - if ( tiffDigestState != kDigestMatches ) ReconcileUtils::ImportTIFF ( *tiff, xmp, tiffDigestState, srcFormat ); - if ( exifDigestState != kDigestMatches ) ReconcileUtils::ImportExif ( *tiff, xmp, exifDigestState ); - if ( iptcDigestState != kDigestMatches ) { - ReconcileUtils::ImportIPTC ( *iptc, xmp, iptcDigestState ); - ReconcileUtils::ImportPSIR ( psir, xmp, iptcDigestState ); - } - - } - - // ! Older versions of Photoshop did not import the UserComment or RelatedSoundFile tags. Note - // ! whether the initial XMP has these tags. Don't delete them from the TIFF when saving unless - // ! they were in the XMP to begin with. Can't do this in ReconcileUtils::ImportExif, that is - // ! only called when the Exif is newer than the XMP. + // If photoshop:DateCreated does not exist try to create it from exif:DateTimeOriginal. - tiff->xmpHadUserComment = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "UserComment" ); - tiff->xmpHadRelatedSoundFile = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "RelatedSoundFile" ); + if ( ! xmp->DoesPropertyExist ( kXMP_NS_Photoshop, "DateCreated" ) ) { + std::string exifValue; + bool haveExifDTO = xmp->GetProperty ( kXMP_NS_EXIF, "DateTimeOriginal", &exifValue, 0 ); + if ( haveExifDTO ) xmp->SetProperty ( kXMP_NS_Photoshop, "DateCreated", exifValue.c_str() ); + } -} // ImportJTPtoXMP +} // ImportPhotoData // ================================================================================================= -// ExportXMPtoJTP -// ============== - -void ExportXMPtoJTP ( XMP_FileFormat destFormat, - SXMPMeta * xmp, - TIFF_Manager * tiff, - PSIR_Manager * psir, - IPTC_Manager * iptc, - XMP_OptionBits options /* = 0 */ ) +// ExportPhotoData +// =============== + +void ExportPhotoData ( XMP_FileFormat destFormat, + SXMPMeta * xmp, + TIFF_Manager * exif, // Pass 0 if not wanted. + IPTC_Manager * iptc, // Pass 0 if not wanted. + PSIR_Manager * psir, // Pass 0 if not wanted. + XMP_OptionBits options /* = 0 */ ) { - XMP_Assert ( xmp != 0 ); XMP_Assert ( (destFormat == kXMP_JPEGFile) || (destFormat == kXMP_TIFFFile) || (destFormat == kXMP_PhotoshopFile) ); - - #if XMP_UNIXBuild - // ! Hack until the legacy-as-local issues are resolved for generic UNIX. - iptc = 0; // Strip IIM from the file. - if ( tiff != 0 ) tiff->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_IPTC ); - if ( psir != 0 ) { - psir->DeleteImgRsrc ( kPSIR_IPTC ); - psir->DeleteImgRsrc ( kPSIR_IPTCDigest ); - } - #endif - // Save the IPTC changed flag specially. SetIPTCDigest will call UpdateMemoryDataSets, which - // will clear the IsChanged flag. Also, UpdateMemoryDataSets can be called twice, once for the - // general IPTC-in-PSIR case and once for the IPTC-as-TIFF-tag case. - - bool iptcChanged = false; - - // Do not write legacy IPTC (IIM) or PSIR in DNG files (which are a variant of TIFF). - - if ( (destFormat == kXMP_TIFFFile) && (tiff != 0) && - tiff->GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, 0 ) ) { - + // Do not write IPTC-IIM or PSIR in DNG files (which are a variant of TIFF). + + if ( (destFormat == kXMP_TIFFFile) && (exif != 0) && + exif->GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, 0 ) ) { + iptc = 0; // These prevent calls to ExportIPTC and ExportPSIR. psir = 0; - - tiff->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_IPTC ); // These remove any existing IPTC and PSIR. - tiff->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_PSIR ); - - } - - // Export the individual metadata items to the legacy forms. The PSIR and IPTC must be done - // before the TIFF and Exif. The PSIR and IPTC have side effects that can modify the XMP, and - // thus the values written to TIFF and Exif. The side effects are the CR<->LF normalization that - // is done to match Photoshop. - - if ( psir != 0) ReconcileUtils::ExportPSIR ( *xmp, psir ); - if ( iptc != 0 ) { - ReconcileUtils::ExportIPTC ( xmp, iptc ); - iptcChanged = iptc->IsChanged(); // ! Do after calling ExportIPTC and before calling SetIPTCDigest. - if ( psir != 0 ) ReconcileUtils::SetIPTCDigest ( iptc, psir ); // ! Do always, in case the digest was missing before. - } + exif->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_IPTC ); // These remove any existing IPTC and PSIR. + exif->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_PSIR ); - if ( tiff != 0 ) { - ReconcileUtils::ExportTIFF ( *xmp, tiff ); - ReconcileUtils::ExportExif ( *xmp, tiff ); - ReconcileUtils::SetTIFFDigest ( *tiff, xmp ); // ! Do always, in case the digest was missing before. - ReconcileUtils::SetExifDigest ( *tiff, xmp ); // ! Do always, in case the digest was missing before. } + + // Export the individual metadata items to the non-XMP forms. Set the IPTC digest whether or not + // it changed, it might not have been present or correct before. + + bool iptcChanged = false; // Save explicitly, internal flag is reset by UpdateMemoryDataSets. + + void * iptcPtr = 0; + XMP_Uns32 iptcLen = 0; - // Now update the collections of metadata, e.g. the IPTC in PSIR 1028 or XMP in TIFF tag 700. - // - All of the formats have the IPTC in the PSIR portion. - // - JPEG has nothing else special. - // - PSD has the XMP and Exif in the PSIR portion. - // - TIFF has the XMP, IPTC, and PSIR in primary IFD tags. Yes, a 2nd copy of the IPTC. - - if ( (iptc != 0) && (psir != 0) && iptcChanged ) { - void* iptcPtr; - XMP_Uns32 iptcLen = iptc->UpdateMemoryDataSets ( &iptcPtr ); - psir->SetImgRsrc ( kPSIR_IPTC, iptcPtr, iptcLen ); + if ( iptc != 0 ) { + PhotoDataUtils::ExportIPTC ( *xmp, iptc ); + iptcChanged = iptc->IsChanged(); + if ( iptcChanged ) iptc->UpdateMemoryDataSets(); + iptcLen = iptc->GetBlockInfo ( &iptcPtr ); + if ( psir != 0 ) PhotoDataUtils::SetIPTCDigest ( iptcPtr, iptcLen, psir ); } - - if ( destFormat == kXMP_PhotoshopFile ) { - XMP_Assert ( psir != 0 ); + if ( exif != 0 ) PhotoDataUtils::ExportExif ( xmp, exif ); + if ( psir != 0 ) PhotoDataUtils::ExportPSIR ( *xmp, psir ); - if ( (tiff != 0) && tiff->IsChanged() ) { - void* exifPtr; - XMP_Uns32 exifLen = tiff->UpdateMemoryStream ( &exifPtr ); - psir->SetImgRsrc ( kPSIR_Exif, exifPtr, exifLen ); - } + // Now update the non-XMP collections of metadata according to the file format. Do not update + // the XMP here, that is done in the file handlers after deciding if an XMP-only in-place + // update should be done. + // - JPEG has the IPTC in PSIR 1028, the Exif and PSIR are marker segments. + // - TIFF has the IPTC and PSIR in primary IFD tags. + // - PSD has everything in PSIRs. + + if ( destFormat == kXMP_JPEGFile ) { + + if ( iptcChanged && (psir != 0) ) psir->SetImgRsrc ( kPSIR_IPTC, iptcPtr, iptcLen ); } else if ( destFormat == kXMP_TIFFFile ) { - - XMP_Assert ( tiff != 0 ); - if ( (iptc != 0) && iptcChanged ) { - void* iptcPtr; - XMP_Uns32 iptcLen = iptc->UpdateMemoryDataSets ( &iptcPtr ); - tiff->SetTag ( kTIFF_PrimaryIFD, kTIFF_IPTC, kTIFF_UndefinedType, iptcLen, iptcPtr ); - } + XMP_Assert ( exif != 0 ); + + if ( iptcChanged ) exif->SetTag ( kTIFF_PrimaryIFD, kTIFF_IPTC, kTIFF_UndefinedType, iptcLen, iptcPtr ); if ( (psir != 0) && psir->IsChanged() ) { void* psirPtr; XMP_Uns32 psirLen = psir->UpdateMemoryResources ( &psirPtr ); - tiff->SetTag ( kTIFF_PrimaryIFD, kTIFF_PSIR, kTIFF_UndefinedType, psirLen, psirPtr ); + exif->SetTag ( kTIFF_PrimaryIFD, kTIFF_PSIR, kTIFF_UndefinedType, psirLen, psirPtr ); + } + + } else if ( destFormat == kXMP_PhotoshopFile ) { + + XMP_Assert ( psir != 0 ); + + if ( iptcChanged ) psir->SetImgRsrc ( kPSIR_IPTC, iptcPtr, iptcLen ); + + if ( (exif != 0) && exif->IsChanged() ) { + void* exifPtr; + XMP_Uns32 exifLen = exif->UpdateMemoryStream ( &exifPtr ); + psir->SetImgRsrc ( kPSIR_Exif, exifPtr, exifLen ); } - + } + + // Strip the tiff: and exif: namespaces from the XMP, we're done with them. Save the Exif + // ISOSpeedRatings if any of the values are over 0xFFFF, the native tag is SHORT. Lower level + // code already kept or stripped the XMP form. + + SXMPMeta savedExif; + SaveExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_TIFF, 0, kXMPUtil_DoAllProperties ); + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_EXIF, 0, kXMPUtil_DoAllProperties ); + + RestoreExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); -} // ExportXMPtoJTP +} // ExportPhotoData diff --git a/source/XMPFiles/FormatSupport/ReconcileLegacy.hpp b/source/XMPFiles/FormatSupport/ReconcileLegacy.hpp index 2542309..59918cf 100644 --- a/source/XMPFiles/FormatSupport/ReconcileLegacy.hpp +++ b/source/XMPFiles/FormatSupport/ReconcileLegacy.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -18,31 +18,16 @@ // ================================================================================================= /// \file ReconcileLegacy.hpp -/// \brief Utilities to reconcile between XMP and legacy metadata forms such as TIFF/Exif and IPTC. +/// \brief Utilities to reconcile between XMP and photo metadata forms such as TIFF/Exif and IPTC. /// // ================================================================================================= -// ImportJTPtoXMP imports legacy metadata for JPEG, TIFF, and Photoshop files into XMP. The caller -// must have already done the file specific processing to select the appropriate sources of the TIFF -// stream, the Photoshop image resources, and the IPTC. +// ImportPhotoData imports TIFF/Exif and IPTC metadata from JPEG, TIFF, and Photoshop files into +// XMP. The caller must have already done the file specific processing to select the appropriate +// sources of the TIFF stream, the Photoshop image resources, and the IPTC. // -// The reconciliation logic used here is not identical to that used in Photoshop CS2, but should be -// similar enough. The details of both approaches are documented in LegacyReconcile.pdf. The logic -// used by Photoshop is more processor and memory intensive. That overhead is acceptable when -// opening a file in Photoshop. Client's like Bridge need a lighter weight approach for quick -// read-only access to the reconciled metadata. - -enum { // JTP "last-seen" legacy priorities from Photoshop. Higher numbers are more important. - kLegacyJTP_None = 0, // No legacy metadata. - kLegacyJTP_JPEG_TIFF_Tags = 1, // A JPEG file with TIFF tags 270, 315, or 33432. - kLegacyJTP_PSIR_IPTC = 2, // IPTC from Photoshop image resource 1028. - kLegacyJTP_PSIR_OldCaption = 3, // Old caption from Photoshop image resource 1008 or 1020. - kLegacyJTP_TIFF_TIFF_Tags = 4, // A TIFF file with TIFF tags 270, 315, or 33432. - kLegacyJTP_TIFF_IPTC = 5, // A TIFF file with TIFF tag 33723. - kLegacyJTP_Mac_pnot = 6, // KeyW and Desc items from Macintosh pnot 0 resource. - kLegacyJTP_ANPA_IPTC = 7 // IPTC from Macintosh ANPA 10000 resource. -}; -typedef XMP_Uns8 RecJTP_LegacyPriority; +// The reconciliation logic used here is based on the Metadata Working Group guidelines. This is a +// simpler approach than used previously - which was modeled after historical Photoshop behavior. enum { // Bits for the options to ImportJTPtoXMP. k2XMP_FileHadXMP = 0x0001, // Set if the file had an XMP packet. @@ -50,77 +35,66 @@ enum { // Bits for the options to ImportJTPtoXMP. k2XMP_FileHadExif = 0x0004 // Set if the file had legacy Exif. }; -extern void ImportJTPtoXMP ( XMP_FileFormat srcFormat, - RecJTP_LegacyPriority lastLegacy, - TIFF_Manager * tiff, // ! Need to modify for UserComment and RelatedSoundFile hack. - const PSIR_Manager & psir, - IPTC_Manager * iptc, // ! Need to modify for UpdateDataSets. - SXMPMeta * xmp, - XMP_OptionBits options = 0 ); +extern void ImportPhotoData ( const TIFF_Manager & exif, + const IPTC_Manager & iptc, + const PSIR_Manager & psir, + int iptcDigestState, + SXMPMeta * xmp, + XMP_OptionBits options = 0 ); -#if 0 // Activate if we want to support the Mac pnot resource. -extern void ImportJTPtoXMP ( XMP_FileFormat srcFormat, - RecJTP_LegacyPriority lastLegacy, - const TIFF_Manager & tiff, - const PSIR_Manager & psir, - IPTC_Manager * iptc, - const void * macKeyW, // The STR# for pnot 0 KeyW item. - const std::string & macDesc, // The TEXT for pnot 0 Desc item. - SXMPMeta * xmp, - XMP_OptionBits options = 0 ); -#endif +// ExportPhotoData exports XMP into TIFF/Exif and IPTC metadata for JPEG, TIFF, and Photoshop files. -// ExportXMPtoJTP exports XMP into legacy metadata for JPEG, TIFF, and Photoshop files. +extern void ExportPhotoData ( XMP_FileFormat destFormat, + SXMPMeta * xmp, + TIFF_Manager * exif, // Pass 0 if not wanted. + IPTC_Manager * iptc, // Pass 0 if not wanted. + PSIR_Manager * psir, // Pass 0 if not wanted. + XMP_OptionBits options = 0 ); -extern void ExportXMPtoJTP ( XMP_FileFormat destFormat, - SXMPMeta * xmp, - TIFF_Manager * tiff, // Pass 0 if not wanted. - PSIR_Manager * psir, // Pass 0 if not wanted. - IPTC_Manager * iptc, // Pass 0 if not wanted. - XMP_OptionBits options = 0 ); +// *** Mapping notes need revision for MWG related changes. // ================================================================================================= // Summary of TIFF/Exif mappings to XMP // ==================================== -// +// // The mapping for each tag is driven mainly by the tag ID, and secondarily by the type. E.g. there // is no blanket rule that all ASCII tags are mapped to simple strings in XMP. Some, such as // SubSecTime or GPSLatitudeRef, are combined with other tags; others, like Flash, are reformated. // However, most tags are in fact mapped in an obvious manner based on their type and count. -// +// // Photoshop practice has been to truncate ASCII tags at the first NUL, not supporting the TIFF // specification's notion of multi-part ASCII values. -// +// // Rational values are mapped to XMP as "num/denom". -// +// // The tags of UNDEFINED type that are mapped to XMP text are either special cases like ExifVersion // or the strings with an explicit encoding like UserComment. -// +// // Latitude and logitude are mapped to XMP as "DDD,MM,SSk" or "DDD,MM.mmk"; k is N, S, E, or W. -// +// // Flash struct in XMP separates the Fired, Return, Mode, Function, and RedEyeMode portions of the // Exif value. Fired, Function, and RedEyeMode are Boolean; Return and Mode are integers. -// +// // The OECF/SFR, CFA, and DeviceSettings tables are described in the XMP spec. -// +// // Instead of iterating through all tags in the various IFDs, it is probably more efficient to have // explicit processing for the tags that get special treatment, and a static table listing those // that get mapped by type and count. The type and count processing will verify that the actual // type and count are as expected, if not the tag is ignored. -// +// // Here are the primary (0th) IFD tags that get special treatment: -// +// // 270, 33432 - ASCII mapped to alt-text['x-default'] // 306 - DateTime master // 315 - ASCII mapped to text seq[1] -// +// // Here are the primary (0th) IFD tags that get mapped by type and count: -// +// // 256, 257, 258, 259, 262, 271, 272, 274, 277, 282, 283, 284, 296, 301, 305, 318, 319, // 529, 530, 531, 532 -// +// // Here are the Exif IFD tags that get special treatment: -// +// // 34856, 41484 - OECF/SFR table // 36864, 40960 - 4 ASCII chars to text // 36867, 36868 - DateTime master @@ -130,22 +104,22 @@ extern void ExportXMPtoJTP ( XMP_FileFormat destFormat, // 41728, 41729 - UInt8 to integer // 41730 - CFA table // 41995 - DeviceSettings table -// +// // Here are the Exif IFD tags that get mapped by type and count: -// +// // 33434, 33437, 34850, 34852, 34855, 37122, 37377, 37378, 37379, 37380, 37381, 37382, 37383, 37384, // 37386, 37396, 40961, 40962, 40963, 40964, 41483, 41486, 41487, 41488, 41492, 41493, 41495, 41985, // 41986, 41987, 41988, 41989, 41990, 41991, 41992, 41993, 41994, 41996, 42016 -// +// // Here are the GPS IFD tags that get special treatment: -// +// // 0 - 4 UInt8 to text "n.n.n.n" // 2, 4, 20, 22 - Latitude or longitude master // 7 - special DateTime master, the time part // 27, 28 - explicitly encoded text -// +// // Here are the GPS IFD tags that get mapped by type and count: -// +// // 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 23, 24, 25, 26, 30 // ================================================================================================= @@ -170,7 +144,7 @@ extern void ExportXMPtoJTP ( XMP_FileFormat destFormat, // // General (primary and thumbnail, 0th and 1st) IFD tags // tag TIFF type count Name XMP mapping -// +// // 256 SHORTorLONG 1 ImageWidth integer // 257 SHORTorLONG 1 ImageLength integer // 258 SHORT 3 BitsPerSample integer seq @@ -196,10 +170,10 @@ extern void ExportXMPtoJTP ( XMP_FileFormat destFormat, // 531 SHORT 1 YCbCrPositioning integer // 532 RATIONAL 6 ReferenceBlackWhite rational seq // 33432 ASCII Any Copyright text, dc:rights['x-default'] -// +// // Exif IFD tags // tag TIFF type count Name XMP mapping -// +// // 33434 RATIONAL 1 ExposureTime rational // 33437 RATIONAL 1 FNumber rational // 34850 SHORT 1 ExposureProgram integer @@ -255,10 +229,10 @@ extern void ExportXMPtoJTP ( XMP_FileFormat destFormat, // 41995 UNDEFINED Any DeviceSettingDescription DeviceSettings table // 41996 SHORT 1 SubjectDistanceRange integer // 42016 ASCII 33 ImageUniqueID text -// +// // GPS IFD tags // tag TIFF type count Name XMP mapping -// +// // 0 BYTE 4 GPSVersionID text, "n.n.n.n", Exif has 4 UInt8 // 1 ASCII 2 GPSLatitudeRef latitude, with 2 // 2 RATIONAL 3 GPSLatitude latitude, master of 2 diff --git a/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp b/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp index 4ae7564..892c683 100644 --- a/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp +++ b/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -19,6 +19,8 @@ #endif #if XMP_WinBuild + #pragma warning ( disable : 4146 ) // unary minus operator applied to unsigned type + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' #pragma warning ( disable : 4996 ) // '...' was declared deprecated #endif @@ -44,132 +46,148 @@ // ! The sentinel tag value can't be 0, that is a valid GPS Info tag, 0xFFFF is unused so far. +enum { + kExport_Never = 0, // Never export. + kExport_Always = 1, // Add, modify, or delete. + kExport_NoDelete = 2, // Add or modify, do not delete if no XMP. + kExport_InjectOnly = 3 // Add tag if new, never modify or delete existing values. +}; + struct TIFF_MappingToXMP { XMP_Uns16 id; XMP_Uns16 type; XMP_Uns32 count; // Zero means any. + XMP_Uns8 exportMode; const char * name; // The name of the mapped XMP property. The namespace is implicit. }; enum { kAnyCount = 0 }; -static const TIFF_MappingToXMP sPrimaryIFDMappings[] = { - { /* 256 */ kTIFF_ImageWidth, kTIFF_ShortOrLongType, 1, "ImageWidth" }, - { /* 257 */ kTIFF_ImageLength, kTIFF_ShortOrLongType, 1, "ImageLength" }, - { /* 258 */ kTIFF_BitsPerSample, kTIFF_ShortType, 3, "BitsPerSample" }, - { /* 259 */ kTIFF_Compression, kTIFF_ShortType, 1, "Compression" }, - { /* 262 */ kTIFF_PhotometricInterpretation, kTIFF_ShortType, 1, "PhotometricInterpretation" }, - { /* 274 */ kTIFF_Orientation, kTIFF_ShortType, 1, "Orientation" }, - { /* 277 */ kTIFF_SamplesPerPixel, kTIFF_ShortType, 1, "SamplesPerPixel" }, - { /* 284 */ kTIFF_PlanarConfiguration, kTIFF_ShortType, 1, "PlanarConfiguration" }, - { /* 530 */ kTIFF_YCbCrSubSampling, kTIFF_ShortType, 2, "YCbCrSubSampling" }, - { /* 531 */ kTIFF_YCbCrPositioning, kTIFF_ShortType, 1, "YCbCrPositioning" }, - { /* 282 */ kTIFF_XResolution, kTIFF_RationalType, 1, "XResolution" }, - { /* 283 */ kTIFF_YResolution, kTIFF_RationalType, 1, "YResolution" }, - { /* 296 */ kTIFF_ResolutionUnit, kTIFF_ShortType, 1, "ResolutionUnit" }, - { /* 301 */ kTIFF_TransferFunction, kTIFF_ShortType, 3*256, "TransferFunction" }, - { /* 318 */ kTIFF_WhitePoint, kTIFF_RationalType, 2, "WhitePoint" }, - { /* 319 */ kTIFF_PrimaryChromaticities, kTIFF_RationalType, 6, "PrimaryChromaticities" }, - { /* 529 */ kTIFF_YCbCrCoefficients, kTIFF_RationalType, 3, "YCbCrCoefficients" }, - { /* 532 */ kTIFF_ReferenceBlackWhite, kTIFF_RationalType, 6, "ReferenceBlackWhite" }, - { /* 306 */ kTIFF_DateTime, kTIFF_ASCIIType, 20, "" }, // ! Has a special mapping. - { /* 270 */ kTIFF_ImageDescription, kTIFF_ASCIIType, kAnyCount, "" }, // ! Has a special mapping. - { /* 271 */ kTIFF_Make, kTIFF_ASCIIType, kAnyCount, "Make" }, - { /* 272 */ kTIFF_Model, kTIFF_ASCIIType, kAnyCount, "Model" }, - { /* 305 */ kTIFF_Software, kTIFF_ASCIIType, kAnyCount, "Software" }, // Has alias to xmp:CreatorTool. - { /* 315 */ kTIFF_Artist, kTIFF_ASCIIType, kAnyCount, "" }, // ! Has a special mapping. - { /* 33432 */ kTIFF_Copyright, kTIFF_ASCIIType, kAnyCount, "" }, +static const TIFF_MappingToXMP sPrimaryIFDMappings[] = { // A blank name indicates a special mapping. + { /* 256 */ kTIFF_ImageWidth, kTIFF_ShortOrLongType, 1, kExport_Never, "ImageWidth" }, + { /* 257 */ kTIFF_ImageLength, kTIFF_ShortOrLongType, 1, kExport_Never, "ImageLength" }, + { /* 258 */ kTIFF_BitsPerSample, kTIFF_ShortType, 3, kExport_Never, "BitsPerSample" }, + { /* 259 */ kTIFF_Compression, kTIFF_ShortType, 1, kExport_Never, "Compression" }, + { /* 262 */ kTIFF_PhotometricInterpretation, kTIFF_ShortType, 1, kExport_Never, "PhotometricInterpretation" }, + { /* 274 */ kTIFF_Orientation, kTIFF_ShortType, 1, kExport_NoDelete, "Orientation" }, + { /* 277 */ kTIFF_SamplesPerPixel, kTIFF_ShortType, 1, kExport_Never, "SamplesPerPixel" }, + { /* 284 */ kTIFF_PlanarConfiguration, kTIFF_ShortType, 1, kExport_Never, "PlanarConfiguration" }, + { /* 530 */ kTIFF_YCbCrSubSampling, kTIFF_ShortType, 2, kExport_Never, "YCbCrSubSampling" }, + { /* 531 */ kTIFF_YCbCrPositioning, kTIFF_ShortType, 1, kExport_Never, "YCbCrPositioning" }, + { /* 282 */ kTIFF_XResolution, kTIFF_RationalType, 1, kExport_NoDelete, "XResolution" }, + { /* 283 */ kTIFF_YResolution, kTIFF_RationalType, 1, kExport_NoDelete, "YResolution" }, + { /* 296 */ kTIFF_ResolutionUnit, kTIFF_ShortType, 1, kExport_NoDelete, "ResolutionUnit" }, + { /* 301 */ kTIFF_TransferFunction, kTIFF_ShortType, 3*256, kExport_Never, "TransferFunction" }, + { /* 318 */ kTIFF_WhitePoint, kTIFF_RationalType, 2, kExport_Never, "WhitePoint" }, + { /* 319 */ kTIFF_PrimaryChromaticities, kTIFF_RationalType, 6, kExport_Never, "PrimaryChromaticities" }, + { /* 529 */ kTIFF_YCbCrCoefficients, kTIFF_RationalType, 3, kExport_Never, "YCbCrCoefficients" }, + { /* 532 */ kTIFF_ReferenceBlackWhite, kTIFF_RationalType, 6, kExport_Never, "ReferenceBlackWhite" }, + { /* 306 */ kTIFF_DateTime, kTIFF_ASCIIType, 20, kExport_Always, "" }, // ! Has a special mapping. + { /* 270 */ kTIFF_ImageDescription, kTIFF_ASCIIType, kAnyCount, kExport_Always, "" }, // ! Has a special mapping. + { /* 271 */ kTIFF_Make, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, "Make" }, + { /* 272 */ kTIFF_Model, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, "Model" }, + { /* 305 */ kTIFF_Software, kTIFF_ASCIIType, kAnyCount, kExport_Always, "Software" }, // Has alias to xmp:CreatorTool. + { /* 315 */ kTIFF_Artist, kTIFF_ASCIIType, kAnyCount, kExport_Always, "" }, // ! Has a special mapping. + { /* 33432 */ kTIFF_Copyright, kTIFF_ASCIIType, kAnyCount, kExport_Always, "" }, // ! Has a special mapping. { 0xFFFF, 0, 0, 0 } // ! Must end with sentinel. }; +// ! A special need, easier than looking up the entry in sExifIFDMappings: +static const TIFF_MappingToXMP kISOSpeedMapping = { kTIFF_ISOSpeedRatings, kTIFF_ShortType, kAnyCount, kExport_InjectOnly, "ISOSpeedRatings" }; + static const TIFF_MappingToXMP sExifIFDMappings[] = { - { /* 36864 */ kTIFF_ExifVersion, kTIFF_UndefinedType, 4, "" }, // ! Has a special mapping. - { /* 40960 */ kTIFF_FlashpixVersion, kTIFF_UndefinedType, 4, "" }, // ! Has a special mapping. - { /* 40961 */ kTIFF_ColorSpace, kTIFF_ShortType, 1, "ColorSpace" }, - { /* 37121 */ kTIFF_ComponentsConfiguration, kTIFF_UndefinedType, 4, "" }, // ! Has a special mapping. - { /* 37122 */ kTIFF_CompressedBitsPerPixel, kTIFF_RationalType, 1, "CompressedBitsPerPixel" }, - { /* 40962 */ kTIFF_PixelXDimension, kTIFF_ShortOrLongType, 1, "PixelXDimension" }, - { /* 40963 */ kTIFF_PixelYDimension, kTIFF_ShortOrLongType, 1, "PixelYDimension" }, - { /* 37510 */ kTIFF_UserComment, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 40964 */ kTIFF_RelatedSoundFile, kTIFF_ASCIIType, 13, "RelatedSoundFile" }, - { /* 36867 */ kTIFF_DateTimeOriginal, kTIFF_ASCIIType, 20, "" }, // ! Has a special mapping. - { /* 36868 */ kTIFF_DateTimeDigitized, kTIFF_ASCIIType, 20, "" }, // ! Has a special mapping. - { /* 33434 */ kTIFF_ExposureTime, kTIFF_RationalType, 1, "ExposureTime" }, - { /* 33437 */ kTIFF_FNumber, kTIFF_RationalType, 1, "FNumber" }, - { /* 34850 */ kTIFF_ExposureProgram, kTIFF_ShortType, 1, "ExposureProgram" }, - { /* 34852 */ kTIFF_SpectralSensitivity, kTIFF_ASCIIType, kAnyCount, "SpectralSensitivity" }, - { /* 34855 */ kTIFF_ISOSpeedRatings, kTIFF_ShortType, kAnyCount, "ISOSpeedRatings" }, - { /* 34856 */ kTIFF_OECF, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 37377 */ kTIFF_ShutterSpeedValue, kTIFF_SRationalType, 1, "ShutterSpeedValue" }, - { /* 37378 */ kTIFF_ApertureValue, kTIFF_RationalType, 1, "ApertureValue" }, - { /* 37379 */ kTIFF_BrightnessValue, kTIFF_SRationalType, 1, "BrightnessValue" }, - { /* 37380 */ kTIFF_ExposureBiasValue, kTIFF_SRationalType, 1, "ExposureBiasValue" }, - { /* 37381 */ kTIFF_MaxApertureValue, kTIFF_RationalType, 1, "MaxApertureValue" }, - { /* 37382 */ kTIFF_SubjectDistance, kTIFF_RationalType, 1, "SubjectDistance" }, - { /* 37383 */ kTIFF_MeteringMode, kTIFF_ShortType, 1, "MeteringMode" }, - { /* 37384 */ kTIFF_LightSource, kTIFF_ShortType, 1, "LightSource" }, - { /* 37385 */ kTIFF_Flash, kTIFF_ShortType, 1, "" }, // ! Has a special mapping. - { /* 37386 */ kTIFF_FocalLength, kTIFF_RationalType, 1, "FocalLength" }, - { /* 37396 */ kTIFF_SubjectArea, kTIFF_ShortType, kAnyCount, "SubjectArea" }, // ! Actually 2..4. - { /* 41483 */ kTIFF_FlashEnergy, kTIFF_RationalType, 1, "FlashEnergy" }, - { /* 41484 */ kTIFF_SpatialFrequencyResponse, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 41486 */ kTIFF_FocalPlaneXResolution, kTIFF_RationalType, 1, "FocalPlaneXResolution" }, - { /* 41487 */ kTIFF_FocalPlaneYResolution, kTIFF_RationalType, 1, "FocalPlaneYResolution" }, - { /* 41488 */ kTIFF_FocalPlaneResolutionUnit, kTIFF_ShortType, 1, "FocalPlaneResolutionUnit" }, - { /* 41492 */ kTIFF_SubjectLocation, kTIFF_ShortType, 2, "SubjectLocation" }, - { /* 41493 */ kTIFF_ExposureIndex, kTIFF_RationalType, 1, "ExposureIndex" }, - { /* 41495 */ kTIFF_SensingMethod, kTIFF_ShortType, 1, "SensingMethod" }, - { /* 41728 */ kTIFF_FileSource, kTIFF_UndefinedType, 1, "" }, // ! Has a special mapping. - { /* 41729 */ kTIFF_SceneType, kTIFF_UndefinedType, 1, "" }, // ! Has a special mapping. - { /* 41730 */ kTIFF_CFAPattern, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 41985 */ kTIFF_CustomRendered, kTIFF_ShortType, 1, "CustomRendered" }, - { /* 41986 */ kTIFF_ExposureMode, kTIFF_ShortType, 1, "ExposureMode" }, - { /* 41987 */ kTIFF_WhiteBalance, kTIFF_ShortType, 1, "WhiteBalance" }, - { /* 41988 */ kTIFF_DigitalZoomRatio, kTIFF_RationalType, 1, "DigitalZoomRatio" }, - { /* 41989 */ kTIFF_FocalLengthIn35mmFilm, kTIFF_ShortType, 1, "FocalLengthIn35mmFilm" }, - { /* 41990 */ kTIFF_SceneCaptureType, kTIFF_ShortType, 1, "SceneCaptureType" }, - { /* 41991 */ kTIFF_GainControl, kTIFF_ShortType, 1, "GainControl" }, - { /* 41992 */ kTIFF_Contrast, kTIFF_ShortType, 1, "Contrast" }, - { /* 41993 */ kTIFF_Saturation, kTIFF_ShortType, 1, "Saturation" }, - { /* 41994 */ kTIFF_Sharpness, kTIFF_ShortType, 1, "Sharpness" }, - { /* 41995 */ kTIFF_DeviceSettingDescription, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 41996 */ kTIFF_SubjectDistanceRange, kTIFF_ShortType, 1, "SubjectDistanceRange" }, - { /* 42016 */ kTIFF_ImageUniqueID, kTIFF_ASCIIType, 33, "ImageUniqueID" }, + { /* 36864 */ kTIFF_ExifVersion, kTIFF_UndefinedType, 4, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 40960 */ kTIFF_FlashpixVersion, kTIFF_UndefinedType, 4, kExport_Never, "" }, // ! Has a special mapping. + { /* 40961 */ kTIFF_ColorSpace, kTIFF_ShortType, 1, kExport_InjectOnly, "ColorSpace" }, + { /* 37121 */ kTIFF_ComponentsConfiguration, kTIFF_UndefinedType, 4, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 37122 */ kTIFF_CompressedBitsPerPixel, kTIFF_RationalType, 1, kExport_InjectOnly, "CompressedBitsPerPixel" }, + { /* 40962 */ kTIFF_PixelXDimension, kTIFF_ShortOrLongType, 1, kExport_InjectOnly, "PixelXDimension" }, + { /* 40963 */ kTIFF_PixelYDimension, kTIFF_ShortOrLongType, 1, kExport_InjectOnly, "PixelYDimension" }, + { /* 37510 */ kTIFF_UserComment, kTIFF_UndefinedType, kAnyCount, kExport_Always, "" }, // ! Has a special mapping. + { /* 40964 */ kTIFF_RelatedSoundFile, kTIFF_ASCIIType, kAnyCount, kExport_Always, "RelatedSoundFile" }, // ! Exif spec says count of 13. + { /* 36867 */ kTIFF_DateTimeOriginal, kTIFF_ASCIIType, 20, kExport_Always, "" }, // ! Has a special mapping. + { /* 36868 */ kTIFF_DateTimeDigitized, kTIFF_ASCIIType, 20, kExport_Always, "" }, // ! Has a special mapping. + { /* 33434 */ kTIFF_ExposureTime, kTIFF_RationalType, 1, kExport_InjectOnly, "ExposureTime" }, + { /* 33437 */ kTIFF_FNumber, kTIFF_RationalType, 1, kExport_InjectOnly, "FNumber" }, + { /* 34850 */ kTIFF_ExposureProgram, kTIFF_ShortType, 1, kExport_InjectOnly, "ExposureProgram" }, + { /* 34852 */ kTIFF_SpectralSensitivity, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, "SpectralSensitivity" }, + { /* 34855 */ kTIFF_ISOSpeedRatings, kTIFF_ShortType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 34856 */ kTIFF_OECF, kTIFF_UndefinedType, kAnyCount, kExport_Never, "" }, // ! Has a special mapping. + { /* 37377 */ kTIFF_ShutterSpeedValue, kTIFF_SRationalType, 1, kExport_InjectOnly, "ShutterSpeedValue" }, + { /* 37378 */ kTIFF_ApertureValue, kTIFF_RationalType, 1, kExport_InjectOnly, "ApertureValue" }, + { /* 37379 */ kTIFF_BrightnessValue, kTIFF_SRationalType, 1, kExport_InjectOnly, "BrightnessValue" }, + { /* 37380 */ kTIFF_ExposureBiasValue, kTIFF_SRationalType, 1, kExport_InjectOnly, "ExposureBiasValue" }, + { /* 37381 */ kTIFF_MaxApertureValue, kTIFF_RationalType, 1, kExport_InjectOnly, "MaxApertureValue" }, + { /* 37382 */ kTIFF_SubjectDistance, kTIFF_RationalType, 1, kExport_InjectOnly, "SubjectDistance" }, + { /* 37383 */ kTIFF_MeteringMode, kTIFF_ShortType, 1, kExport_InjectOnly, "MeteringMode" }, + { /* 37384 */ kTIFF_LightSource, kTIFF_ShortType, 1, kExport_InjectOnly, "LightSource" }, + { /* 37385 */ kTIFF_Flash, kTIFF_ShortType, 1, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 37386 */ kTIFF_FocalLength, kTIFF_RationalType, 1, kExport_InjectOnly, "FocalLength" }, + { /* 37396 */ kTIFF_SubjectArea, kTIFF_ShortType, kAnyCount, kExport_Never, "SubjectArea" }, // ! Actually 2..4. + { /* 41483 */ kTIFF_FlashEnergy, kTIFF_RationalType, 1, kExport_InjectOnly, "FlashEnergy" }, + { /* 41484 */ kTIFF_SpatialFrequencyResponse, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 41486 */ kTIFF_FocalPlaneXResolution, kTIFF_RationalType, 1, kExport_InjectOnly, "FocalPlaneXResolution" }, + { /* 41487 */ kTIFF_FocalPlaneYResolution, kTIFF_RationalType, 1, kExport_InjectOnly, "FocalPlaneYResolution" }, + { /* 41488 */ kTIFF_FocalPlaneResolutionUnit, kTIFF_ShortType, 1, kExport_InjectOnly, "FocalPlaneResolutionUnit" }, + { /* 41492 */ kTIFF_SubjectLocation, kTIFF_ShortType, 2, kExport_Never, "SubjectLocation" }, + { /* 41493 */ kTIFF_ExposureIndex, kTIFF_RationalType, 1, kExport_InjectOnly, "ExposureIndex" }, + { /* 41495 */ kTIFF_SensingMethod, kTIFF_ShortType, 1, kExport_InjectOnly, "SensingMethod" }, + { /* 41728 */ kTIFF_FileSource, kTIFF_UndefinedType, 1, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 41729 */ kTIFF_SceneType, kTIFF_UndefinedType, 1, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 41730 */ kTIFF_CFAPattern, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 41985 */ kTIFF_CustomRendered, kTIFF_ShortType, 1, kExport_Never, "CustomRendered" }, + { /* 41986 */ kTIFF_ExposureMode, kTIFF_ShortType, 1, kExport_InjectOnly, "ExposureMode" }, + { /* 41987 */ kTIFF_WhiteBalance, kTIFF_ShortType, 1, kExport_InjectOnly, "WhiteBalance" }, + { /* 41988 */ kTIFF_DigitalZoomRatio, kTIFF_RationalType, 1, kExport_InjectOnly, "DigitalZoomRatio" }, + { /* 41989 */ kTIFF_FocalLengthIn35mmFilm, kTIFF_ShortType, 1, kExport_InjectOnly, "FocalLengthIn35mmFilm" }, + { /* 41990 */ kTIFF_SceneCaptureType, kTIFF_ShortType, 1, kExport_InjectOnly, "SceneCaptureType" }, + { /* 41991 */ kTIFF_GainControl, kTIFF_ShortType, 1, kExport_InjectOnly, "GainControl" }, + { /* 41992 */ kTIFF_Contrast, kTIFF_ShortType, 1, kExport_InjectOnly, "Contrast" }, + { /* 41993 */ kTIFF_Saturation, kTIFF_ShortType, 1, kExport_InjectOnly, "Saturation" }, + { /* 41994 */ kTIFF_Sharpness, kTIFF_ShortType, 1, kExport_InjectOnly, "Sharpness" }, + { /* 41995 */ kTIFF_DeviceSettingDescription, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 41996 */ kTIFF_SubjectDistanceRange, kTIFF_ShortType, 1, kExport_InjectOnly, "SubjectDistanceRange" }, + { /* 42016 */ kTIFF_ImageUniqueID, kTIFF_ASCIIType, 33, kExport_InjectOnly, "ImageUniqueID" }, { 0xFFFF, 0, 0, 0 } // ! Must end with sentinel. }; static const TIFF_MappingToXMP sGPSInfoIFDMappings[] = { - { /* 0 */ kTIFF_GPSVersionID, kTIFF_ByteType, 4, "" }, // ! Has a special mapping. - { /* 2 */ kTIFF_GPSLatitude, kTIFF_RationalType, 3, "" }, // ! Has a special mapping. - { /* 4 */ kTIFF_GPSLongitude, kTIFF_RationalType, 3, "" }, // ! Has a special mapping. - { /* 5 */ kTIFF_GPSAltitudeRef, kTIFF_ByteType, 1, "GPSAltitudeRef" }, - { /* 6 */ kTIFF_GPSAltitude, kTIFF_RationalType, 1, "GPSAltitude" }, - { /* 7 */ kTIFF_GPSTimeStamp, kTIFF_RationalType, 3, "" }, // ! Has a special mapping. - { /* 8 */ kTIFF_GPSSatellites, kTIFF_ASCIIType, kAnyCount, "GPSSatellites" }, - { /* 9 */ kTIFF_GPSStatus, kTIFF_ASCIIType, 2, "GPSStatus" }, - { /* 10 */ kTIFF_GPSMeasureMode, kTIFF_ASCIIType, 2, "GPSMeasureMode" }, - { /* 11 */ kTIFF_GPSDOP, kTIFF_RationalType, 1, "GPSDOP" }, - { /* 12 */ kTIFF_GPSSpeedRef, kTIFF_ASCIIType, 2, "GPSSpeedRef" }, - { /* 13 */ kTIFF_GPSSpeed, kTIFF_RationalType, 1, "GPSSpeed" }, - { /* 14 */ kTIFF_GPSTrackRef, kTIFF_ASCIIType, 2, "GPSTrackRef" }, - { /* 15 */ kTIFF_GPSTrack, kTIFF_RationalType, 1, "GPSTrack" }, - { /* 16 */ kTIFF_GPSImgDirectionRef, kTIFF_ASCIIType, 2, "GPSImgDirectionRef" }, - { /* 17 */ kTIFF_GPSImgDirection, kTIFF_RationalType, 1, "GPSImgDirection" }, - { /* 18 */ kTIFF_GPSMapDatum, kTIFF_ASCIIType, kAnyCount, "GPSMapDatum" }, - { /* 20 */ kTIFF_GPSDestLatitude, kTIFF_RationalType, 3, "" }, // ! Has a special mapping. - { /* 22 */ kTIFF_GPSDestLongitude, kTIFF_RationalType, 3, "" }, // ! Has a special mapping. - { /* 23 */ kTIFF_GPSDestBearingRef, kTIFF_ASCIIType, 2, "GPSDestBearingRef" }, - { /* 24 */ kTIFF_GPSDestBearing, kTIFF_RationalType, 1, "GPSDestBearing" }, - { /* 25 */ kTIFF_GPSDestDistanceRef, kTIFF_ASCIIType, 2, "GPSDestDistanceRef" }, - { /* 26 */ kTIFF_GPSDestDistance, kTIFF_RationalType, 1, "GPSDestDistance" }, - { /* 27 */ kTIFF_GPSProcessingMethod, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 28 */ kTIFF_GPSAreaInformation, kTIFF_UndefinedType, kAnyCount, "" }, // ! Has a special mapping. - { /* 30 */ kTIFF_GPSDifferential, kTIFF_ShortType, 1, "GPSDifferential" }, + { /* 0 */ kTIFF_GPSVersionID, kTIFF_ByteType, 4, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 2 */ kTIFF_GPSLatitude, kTIFF_RationalType, 3, kExport_Always, "" }, // ! Has a special mapping. + { /* 4 */ kTIFF_GPSLongitude, kTIFF_RationalType, 3, kExport_Always, "" }, // ! Has a special mapping. + { /* 5 */ kTIFF_GPSAltitudeRef, kTIFF_ByteType, 1, kExport_Always, "GPSAltitudeRef" }, + { /* 6 */ kTIFF_GPSAltitude, kTIFF_RationalType, 1, kExport_Always, "GPSAltitude" }, + { /* 7 */ kTIFF_GPSTimeStamp, kTIFF_RationalType, 3, kExport_Always, "" }, // ! Has a special mapping. + { /* 8 */ kTIFF_GPSSatellites, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, "GPSSatellites" }, + { /* 9 */ kTIFF_GPSStatus, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSStatus" }, + { /* 10 */ kTIFF_GPSMeasureMode, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSMeasureMode" }, + { /* 11 */ kTIFF_GPSDOP, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSDOP" }, + { /* 12 */ kTIFF_GPSSpeedRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSSpeedRef" }, + { /* 13 */ kTIFF_GPSSpeed, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSSpeed" }, + { /* 14 */ kTIFF_GPSTrackRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSTrackRef" }, + { /* 15 */ kTIFF_GPSTrack, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSTrack" }, + { /* 16 */ kTIFF_GPSImgDirectionRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSImgDirectionRef" }, + { /* 17 */ kTIFF_GPSImgDirection, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSImgDirection" }, + { /* 18 */ kTIFF_GPSMapDatum, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, "GPSMapDatum" }, + { /* 20 */ kTIFF_GPSDestLatitude, kTIFF_RationalType, 3, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 22 */ kTIFF_GPSDestLongitude, kTIFF_RationalType, 3, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 23 */ kTIFF_GPSDestBearingRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSDestBearingRef" }, + { /* 24 */ kTIFF_GPSDestBearing, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSDestBearing" }, + { /* 25 */ kTIFF_GPSDestDistanceRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, "GPSDestDistanceRef" }, + { /* 26 */ kTIFF_GPSDestDistance, kTIFF_RationalType, 1, kExport_InjectOnly, "GPSDestDistance" }, + { /* 27 */ kTIFF_GPSProcessingMethod, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 28 */ kTIFF_GPSAreaInformation, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "" }, // ! Has a special mapping. + { /* 30 */ kTIFF_GPSDifferential, kTIFF_ShortType, 1, kExport_InjectOnly, "GPSDifferential" }, { 0xFFFF, 0, 0, 0 } // ! Must end with sentinel. }; // ================================================================================================= +static void // ! Needed by Import2WayExif +ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, TIFF_Manager * tiff, XMP_Uns16 mainID ); + +// ================================================================================================= + static XMP_Uns32 GatherInt ( const char * strPtr, size_t count ) { XMP_Uns32 value = 0; @@ -184,178 +202,142 @@ static XMP_Uns32 GatherInt ( const char * strPtr, size_t count ) return value; -} +} // GatherInt // ================================================================================================= -// ================================================================================================= - -// ================================================================================================= -// ComputeTIFFDigest -// ================= -// -// Compute a 128 bit (16 byte) MD5 digest of the mapped TIFF tags and format it as a string like: -// 256,257,...;A0FCE844924381619820B6F7117C8B83 -// The first portion is a decimal list of the tags from sPrimaryIFDMappings, the last part is the -// MD5 digest as 32 hex digits using capital A-F. - -// ! The order of listing for the tags is crucial for the change comparisons to work! -static void -ComputeTIFFDigest ( const TIFF_Manager & tiff, std::string * digestStr ) +static void TrimTrailingSpaces ( TIFF_Manager::TagInfo * info ) { - MD5_CTX context; - MD5_Digest digest; - char buffer[40]; - size_t in, out; + if ( info->dataLen == 0 ) return; + XMP_Assert ( info->dataPtr != 0 ); - TIFF_Manager::TagInfo tagInfo; - - MD5Init ( &context ); - digestStr->clear(); - digestStr->reserve ( 160 ); // The current length is 134. - - for ( size_t i = 0; sPrimaryIFDMappings[i].id != 0xFFFF; ++i ) { - snprintf ( buffer, sizeof(buffer), "%d,", sPrimaryIFDMappings[i].id ); // AUDIT: Use of sizeof(buffer) is safe. - digestStr->append ( buffer ); - bool found = tiff.GetTag ( kTIFF_PrimaryIFD, sPrimaryIFDMappings[i].id, &tagInfo ); - if ( found ) MD5Update ( &context, (XMP_Uns8*)tagInfo.dataPtr, tagInfo.dataLen ); - } - - size_t endPos = digestStr->size() - 1; - (*digestStr)[endPos] = ';'; - - MD5Final ( digest, &context ); - - for ( in = 0, out = 0; in < 16; in += 1, out += 2 ) { - XMP_Uns8 byte = digest[in]; - buffer[out] = ReconcileUtils::kHexDigits [ byte >> 4 ]; - buffer[out+1] = ReconcileUtils::kHexDigits [ byte & 0xF ]; - } - buffer[32] = 0; + char * firstChar = (char*)info->dataPtr; + char * lastChar = firstChar + info->dataLen - 1; + + if ( (*lastChar != ' ') && (*lastChar != 0) ) return; // Nothing to do. + + while ( (firstChar <= lastChar) && ((*lastChar == ' ') || (*lastChar == 0)) ) --lastChar; + + XMP_Assert ( (lastChar == firstChar-1) || + ((lastChar >= firstChar) && (*lastChar != ' ') && (*lastChar != 0)) ); + + ++lastChar; + XMP_Uns32 newLen = (XMP_Uns32)(lastChar - firstChar) + 1; + XMP_Assert ( newLen <= info->dataLen ); - digestStr->append ( buffer ); + *lastChar = 0; + info->dataLen = newLen; -} // ComputeTIFFDigest; +} // TrimTrailingSpaces // ================================================================================================= -// ComputeExifDigest -// ================= -// -// Compute a 128 bit (16 byte) MD5 digest of the mapped Exif andf GPS tags and format it as a string like: -// 36864,40960,...;A0FCE844924381619820B6F7117C8B83 -// The first portion is a decimal list of the tags, the last part is the MD5 digest as 32 hex -// digits using capital A-F. The listed tags are those from sExifIFDMappings followed by those from -// sGPSInfoIFDMappings. - -// ! The order of listing for the tags is crucial for the change comparisons to work! -static void -ComputeExifDigest ( const TIFF_Manager & exif, std::string * digestStr ) +bool PhotoDataUtils::GetNativeInfo ( const TIFF_Manager & exif, XMP_Uns8 ifd, XMP_Uns16 id, TIFF_Manager::TagInfo * info ) { - MD5_CTX context; - MD5_Digest digest; - char buffer[40]; - size_t in, out; + bool haveExif = exif.GetTag ( ifd, id, info ); - TIFF_Manager::TagInfo tagInfo; - - MD5Init ( &context ); - digestStr->clear(); - digestStr->reserve ( 440 ); // The current length is 414. + if ( haveExif ) { - for ( size_t i = 0; sExifIFDMappings[i].id != 0xFFFF; ++i ) { - snprintf ( buffer, sizeof(buffer), "%d,", sExifIFDMappings[i].id ); // AUDIT: Use of sizeof(buffer) is safe. - digestStr->append ( buffer ); - bool found = exif.GetTag ( kTIFF_ExifIFD, sExifIFDMappings[i].id, &tagInfo ); - if ( found ) MD5Update ( &context, (XMP_Uns8*)tagInfo.dataPtr, tagInfo.dataLen ); - } + XMP_Uns32 i; + char * chPtr; + + XMP_Assert ( (info->dataPtr != 0) || (info->dataLen == 0) ); // Null pointer requires zero length. - for ( size_t i = 0; sGPSInfoIFDMappings[i].id != 0xFFFF; ++i ) { - snprintf ( buffer, sizeof(buffer), "%d,", sGPSInfoIFDMappings[i].id ); // AUDIT: Use of sizeof(buffer) is safe. - digestStr->append ( buffer ); - bool found = exif.GetTag ( kTIFF_GPSInfoIFD, sGPSInfoIFDMappings[i].id, &tagInfo ); - if ( found ) MD5Update ( &context, (XMP_Uns8*)tagInfo.dataPtr, tagInfo.dataLen ); - } + bool isDate = ((id == kTIFF_DateTime) || (id == kTIFF_DateTimeOriginal) || (id == kTIFF_DateTimeOriginal)); - size_t endPos = digestStr->size() - 1; - (*digestStr)[endPos] = ';'; + for ( i = 0, chPtr = (char*)info->dataPtr; i < info->dataLen; ++i, ++chPtr ) { + if ( isDate && (*chPtr == ':') ) continue; // Ignore colons, empty dates have spaces and colons. + if ( (*chPtr != ' ') && (*chPtr != 0) ) break; // Break if the Exif value is non-empty. + } - MD5Final ( digest, &context ); + if ( i == info->dataLen ) { + haveExif = false; // Ignore empty Exif. + } else { + TrimTrailingSpaces ( info ); + if ( info->dataLen == 0 ) haveExif = false; + } - for ( in = 0, out = 0; in < 16; in += 1, out += 2 ) { - XMP_Uns8 byte = digest[in]; - buffer[out] = ReconcileUtils::kHexDigits [ byte >> 4 ]; - buffer[out+1] = ReconcileUtils::kHexDigits [ byte & 0xF ]; } - buffer[32] = 0; - digestStr->append ( buffer ); + return haveExif; -} // ComputeExifDigest; +} // PhotoDataUtils::GetNativeInfo // ================================================================================================= -// ReconcileUtils::CheckTIFFDigest -// =============================== -int -ReconcileUtils::CheckTIFFDigest ( const TIFF_Manager & tiff, const SXMPMeta & xmp ) +size_t PhotoDataUtils::GetNativeInfo ( const IPTC_Manager & iptc, XMP_Uns8 id, int digestState, bool haveXMP, IPTC_Manager::DataSetInfo * info ) { - std::string newDigest, oldDigest; + size_t iptcCount = 0; - ComputeTIFFDigest ( tiff, &newDigest ); - bool found = xmp.GetProperty ( kXMP_NS_TIFF, "NativeDigest", &oldDigest, 0 ); + if ( (digestState == kDigestDiffers) || ((digestState == kDigestMissing) && (! haveXMP)) ) { + iptcCount = iptc.GetDataSet ( id, info ); + } + + if ( ignoreLocalText && (iptcCount > 0) && (! iptc.UsingUTF8()) ) { + // Check to see if the new value(s) should be ignored. + size_t i; + IPTC_Manager::DataSetInfo tmpInfo; + for ( i = 0; i < iptcCount; ++i ) { + (void) iptc.GetDataSet ( id, &tmpInfo, i ); + if ( ReconcileUtils::IsASCII ( tmpInfo.dataPtr, tmpInfo.dataLen ) ) break; + } + if ( i == iptcCount ) iptcCount = 0; // Return 0 if value(s) should be ignored. + } - if ( ! found ) return kDigestMissing; - if ( newDigest == oldDigest ) return kDigestMatches; - return kDigestDiffers; + return iptcCount; -} // ReconcileUtils::CheckTIFFDigest; +} // PhotoDataUtils::GetNativeInfo // ================================================================================================= -// ReconcileUtils::CheckExifDigest -// =============================== -int -ReconcileUtils::CheckExifDigest ( const TIFF_Manager & tiff, const SXMPMeta & xmp ) +bool PhotoDataUtils::IsValueDifferent ( const TIFF_Manager::TagInfo & exifInfo, const std::string & xmpValue, std::string * exifValue ) { - std::string newDigest, oldDigest; + if ( exifInfo.dataLen == 0 ) return false; // Ignore empty Exif values. - ComputeExifDigest ( tiff, &newDigest ); - bool found = xmp.GetProperty ( kXMP_NS_EXIF, "NativeDigest", &oldDigest, 0 ); + if ( ReconcileUtils::IsUTF8 ( exifInfo.dataPtr, exifInfo.dataLen ) ) { // ! Note that ASCII is UTF-8. + exifValue->assign ( (char*)exifInfo.dataPtr, exifInfo.dataLen ); + } else { + if ( ignoreLocalText ) return false; + ReconcileUtils::LocalToUTF8 ( exifInfo.dataPtr, exifInfo.dataLen, exifValue ); + } - if ( ! found ) return kDigestMissing; - if ( newDigest == oldDigest ) return kDigestMatches; - return kDigestDiffers; + return (*exifValue != xmpValue); -} // ReconcileUtils::CheckExifDigest; +} // PhotoDataUtils::IsValueDifferent // ================================================================================================= -// ReconcileUtils::SetTIFFDigest -// ============================= -void -ReconcileUtils::SetTIFFDigest ( const TIFF_Manager & tiff, SXMPMeta * xmp ) +bool PhotoDataUtils::IsValueDifferent ( const IPTC_Manager & newIPTC, const IPTC_Manager & oldIPTC, XMP_Uns8 id ) { - std::string newDigest; + IPTC_Manager::DataSetInfo newInfo; + size_t newCount = newIPTC.GetDataSet ( id, &newInfo ); + if ( newCount == 0 ) return false; // Ignore missing new IPTC values. - ComputeTIFFDigest ( tiff, &newDigest ); - xmp->SetProperty ( kXMP_NS_TIFF, "NativeDigest", newDigest.c_str() ); + IPTC_Manager::DataSetInfo oldInfo; + size_t oldCount = oldIPTC.GetDataSet ( id, &oldInfo ); + if ( oldCount == 0 ) return true; // Missing old IPTC values differ. + + if ( newCount != oldCount ) return true; -} // ReconcileUtils::SetTIFFDigest; + std::string oldStr, newStr; -// ================================================================================================= -// ReconcileUtils::SetExifDigest -// ============================= + for ( newCount = 0; newCount < oldCount; ++newCount ) { -void -ReconcileUtils::SetExifDigest ( const TIFF_Manager & tiff, SXMPMeta * xmp ) -{ - std::string newDigest; + if ( ignoreLocalText & (! newIPTC.UsingUTF8()) ) { // Check to see if the new value should be ignored. + (void) newIPTC.GetDataSet ( id, &newInfo, newCount ); + if ( ! ReconcileUtils::IsASCII ( newInfo.dataPtr, newInfo.dataLen ) ) continue; + } + + (void) newIPTC.GetDataSet_UTF8 ( id, &newStr, newCount ); + (void) oldIPTC.GetDataSet_UTF8 ( id, &oldStr, newCount ); + if ( newStr.size() == 0 ) continue; // Ignore empty new IPTC. + if ( newStr != oldStr ) break; - ComputeExifDigest ( tiff, &newDigest ); - xmp->SetProperty ( kXMP_NS_EXIF, "NativeDigest", newDigest.c_str() ); + } -} // ReconcileUtils::SetExifDigest; + return ( newCount != oldCount ); // Not different if all values matched. + +} // PhotoDataUtils::IsValueDifferent // ================================================================================================= // ================================================================================================= @@ -372,10 +354,10 @@ ImportSingleTIFF_Short ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ XMP_Uns16 binValue = *((XMP_Uns16*)tagInfo.dataPtr); if ( ! nativeEndian ) binValue = Flip2 ( binValue ); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -397,10 +379,10 @@ ImportSingleTIFF_Long ( const TIFF_Manager::TagInfo & tagInfo, const bool native XMP_Uns32 binValue = *((XMP_Uns32*)tagInfo.dataPtr); if ( ! nativeEndian ) binValue = Flip4 ( binValue ); - + char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%lu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%lu", (unsigned long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -427,10 +409,10 @@ ImportSingleTIFF_Rational ( const TIFF_Manager::TagInfo & tagInfo, const bool na binNum = Flip4 ( binNum ); binDenom = Flip4 ( binDenom ); } - + char strValue[40]; - snprintf ( strValue, sizeof(strValue), "%lu/%lu", binNum, binDenom ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%lu/%lu", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -457,10 +439,10 @@ ImportSingleTIFF_SRational ( const TIFF_Manager::TagInfo & tagInfo, const bool n Flip4 ( &binNum ); Flip4 ( &binDenom ); } - + char strValue[40]; - snprintf ( strValue, sizeof(strValue), "%ld/%ld", binNum, binDenom ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%ld/%ld", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -479,11 +461,14 @@ ImportSingleTIFF_ASCII ( const TIFF_Manager::TagInfo & tagInfo, SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) { try { // Don't let errors with one stop the others. + + TrimTrailingSpaces ( (TIFF_Manager::TagInfo*) &tagInfo ); + if ( tagInfo.dataLen == 0 ) return; // Ignore empty tags. const char * chPtr = (const char *)tagInfo.dataPtr; const bool hasNul = (chPtr[tagInfo.dataLen-1] == 0); const bool isUTF8 = ReconcileUtils::IsUTF8 ( chPtr, tagInfo.dataLen ); - + if ( isUTF8 && hasNul ) { xmp->SetProperty ( xmpNS, xmpProp, chPtr ); } else { @@ -491,11 +476,8 @@ ImportSingleTIFF_ASCII ( const TIFF_Manager::TagInfo & tagInfo, if ( isUTF8 ) { strValue.assign ( chPtr, tagInfo.dataLen ); } else { - #if ! XMP_UNIXBuild - ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); - #else - return; // ! Hack until legacy-as-local issues are resolved for generic UNIX. - #endif + if ( ignoreLocalText ) return; + ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); } xmp->SetProperty ( xmpNS, xmpProp, strValue.c_str() ); } @@ -518,10 +500,10 @@ ImportSingleTIFF_Byte ( const TIFF_Manager::TagInfo & tagInfo, try { // Don't let errors with one stop the others. XMP_Uns8 binValue = *((XMP_Uns8*)tagInfo.dataPtr); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -542,10 +524,10 @@ ImportSingleTIFF_SByte ( const TIFF_Manager::TagInfo & tagInfo, try { // Don't let errors with one stop the others. XMP_Int8 binValue = *((XMP_Int8*)tagInfo.dataPtr); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -567,10 +549,10 @@ ImportSingleTIFF_SShort ( const TIFF_Manager::TagInfo & tagInfo, const bool nati XMP_Int16 binValue = *((XMP_Int16*)tagInfo.dataPtr); if ( ! nativeEndian ) Flip2 ( &binValue ); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -592,10 +574,10 @@ ImportSingleTIFF_SLong ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ XMP_Int32 binValue = *((XMP_Int32*)tagInfo.dataPtr); if ( ! nativeEndian ) Flip4 ( &binValue ); - + char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%ld", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%ld", (long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + xmp->SetProperty ( xmpNS, xmpProp, strValue ); } catch ( ... ) { @@ -617,7 +599,7 @@ ImportSingleTIFF_Float ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ float binValue = *((float*)tagInfo.dataPtr); if ( ! nativeEndian ) Flip4 ( &binValue ); - + xmp->SetProperty_Float ( xmpNS, xmpProp, binValue ); } catch ( ... ) { @@ -639,7 +621,7 @@ ImportSingleTIFF_Double ( const TIFF_Manager::TagInfo & tagInfo, const bool nati double binValue = *((double*)tagInfo.dataPtr); if ( ! nativeEndian ) Flip8 ( &binValue ); - + xmp->SetProperty_Float ( xmpNS, xmpProp, binValue ); // ! Yes, SetProperty_Float. } catch ( ... ) { @@ -727,17 +709,19 @@ ImportArrayTIFF_Short ( const TIFF_Manager::TagInfo & tagInfo, const bool native try { // Don't let errors with one stop the others. XMP_Uns16 * binPtr = (XMP_Uns16*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Uns16 binValue = *binPtr; if ( ! nativeEndian ) binValue = Flip2 ( binValue ); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -758,17 +742,19 @@ ImportArrayTIFF_Long ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeE try { // Don't let errors with one stop the others. XMP_Uns32 * binPtr = (XMP_Uns32*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Uns32 binValue = *binPtr; if ( ! nativeEndian ) binValue = Flip4 ( binValue ); - + char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%lu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%lu", (unsigned long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -789,21 +775,23 @@ ImportArrayTIFF_Rational ( const TIFF_Manager::TagInfo & tagInfo, const bool nat try { // Don't let errors with one stop the others. XMP_Uns32 * binPtr = (XMP_Uns32*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, binPtr += 2 ) { - + XMP_Uns32 binNum = binPtr[0]; XMP_Uns32 binDenom = binPtr[1]; if ( ! nativeEndian ) { binNum = Flip4 ( binNum ); binDenom = Flip4 ( binDenom ); } - + char strValue[40]; - snprintf ( strValue, sizeof(strValue), "%lu/%lu", binNum, binDenom ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%lu/%lu", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -824,21 +812,23 @@ ImportArrayTIFF_SRational ( const TIFF_Manager::TagInfo & tagInfo, const bool na try { // Don't let errors with one stop the others. XMP_Int32 * binPtr = (XMP_Int32*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, binPtr += 2 ) { - + XMP_Int32 binNum = binPtr[0]; XMP_Int32 binDenom = binPtr[1]; if ( ! nativeEndian ) { Flip4 ( &binNum ); Flip4 ( &binDenom ); } - + char strValue[40]; - snprintf ( strValue, sizeof(strValue), "%ld/%ld", binNum, binDenom ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%ld/%ld", (long)binNum, (long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -857,28 +847,30 @@ ImportArrayTIFF_ASCII ( const TIFF_Manager::TagInfo & tagInfo, SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) { try { // Don't let errors with one stop the others. + + TrimTrailingSpaces ( (TIFF_Manager::TagInfo*) &tagInfo ); + if ( tagInfo.dataLen == 0 ) return; // Ignore empty tags. const char * chPtr = (const char *)tagInfo.dataPtr; const char * chEnd = chPtr + tagInfo.dataLen; const bool hasNul = (chPtr[tagInfo.dataLen-1] == 0); const bool isUTF8 = ReconcileUtils::IsUTF8 ( chPtr, tagInfo.dataLen ); - + std::string strValue; - + if ( (! isUTF8) || (! hasNul) ) { if ( isUTF8 ) { strValue.assign ( chPtr, tagInfo.dataLen ); } else { - #if ! XMP_UNIXBuild - ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); - #else - return; // ! Hack until legacy-as-local issues are resolved for generic UNIX. - #endif + if ( ignoreLocalText ) return; + ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); } chPtr = strValue.c_str(); chEnd = chPtr + strValue.size(); } - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( ; chPtr < chEnd; chPtr += (strlen(chPtr) + 1) ) { xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, chPtr ); } @@ -901,16 +893,18 @@ ImportArrayTIFF_Byte ( const TIFF_Manager::TagInfo & tagInfo, try { // Don't let errors with one stop the others. XMP_Uns8 * binPtr = (XMP_Uns8*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Uns8 binValue = *binPtr; - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -931,16 +925,18 @@ ImportArrayTIFF_SByte ( const TIFF_Manager::TagInfo & tagInfo, try { // Don't let errors with one stop the others. XMP_Int8 * binPtr = (XMP_Int8*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Int8 binValue = *binPtr; - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -961,17 +957,19 @@ ImportArrayTIFF_SShort ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ try { // Don't let errors with one stop the others. XMP_Int16 * binPtr = (XMP_Int16*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Int16 binValue = *binPtr; if ( ! nativeEndian ) Flip2 ( &binValue ); - + char strValue[20]; snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -992,17 +990,19 @@ ImportArrayTIFF_SLong ( const TIFF_Manager::TagInfo & tagInfo, const bool native try { // Don't let errors with one stop the others. XMP_Int32 * binPtr = (XMP_Int32*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + XMP_Int32 binValue = *binPtr; if ( ! nativeEndian ) Flip4 ( &binValue ); - + char strValue[20]; - snprintf ( strValue, sizeof(strValue), "%ld", binValue ); // AUDIT: Using sizeof(strValue) is safe. - + snprintf ( strValue, sizeof(strValue), "%ld", (long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); - + } } catch ( ... ) { @@ -1023,17 +1023,19 @@ ImportArrayTIFF_Float ( const TIFF_Manager::TagInfo & tagInfo, const bool native try { // Don't let errors with one stop the others. float * binPtr = (float*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + float binValue = *binPtr; if ( ! nativeEndian ) Flip4 ( &binValue ); - + std::string strValue; SXMPUtils::ConvertFromFloat ( binValue, "", &strValue ); - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue.c_str() ); - + } } catch ( ... ) { @@ -1054,17 +1056,19 @@ ImportArrayTIFF_Double ( const TIFF_Manager::TagInfo & tagInfo, const bool nativ try { // Don't let errors with one stop the others. double * binPtr = (double*)tagInfo.dataPtr; - + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { - + double binValue = *binPtr; if ( ! nativeEndian ) Flip8 ( &binValue ); - + std::string strValue; SXMPUtils::ConvertFromFloat ( binValue, "", &strValue ); // ! Yes, ConvertFromFloat. - + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue.c_str() ); - + } } catch ( ... ) { @@ -1139,45 +1143,11 @@ ImportArrayTIFF ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian } // ImportArrayTIFF // ================================================================================================= -// ImportTIFF_VerifyImport -// ======================= -// -// Decide whether to proceed with the import based on the digest state and presence of the legacy -// and XMP. Will also delete existing XMP if appropriate. - -static bool -ImportTIFF_VerifyImport ( const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState, - XMP_Uns8 tiffIFD, XMP_Uns16 tiffID, const char * xmpNS, const char * xmpProp, - TIFF_Manager::TagInfo * tagInfo ) -{ - bool found = false; - - try { // Don't let errors with one stop the others. - - if ( digestState == kDigestDiffers ) { - xmp->DeleteProperty ( xmpNS, xmpProp ); - } else { - XMP_Assert ( digestState == kDigestMissing ); - if ( xmp->DoesPropertyExist ( xmpNS, xmpProp ) ) return false; - } - - found = tiff.GetTag ( tiffIFD, tiffID, tagInfo ); - - } catch ( ... ) { - found = false; - } - - return found; - -} // ImportTIFF_VerifyImport - -// ================================================================================================= // ImportTIFF_CheckStandardMapping // =============================== static bool -ImportTIFF_CheckStandardMapping ( const TIFF_Manager::TagInfo & tagInfo, - const TIFF_MappingToXMP & mapInfo ) +ImportTIFF_CheckStandardMapping ( const TIFF_Manager::TagInfo & tagInfo, const TIFF_MappingToXMP & mapInfo ) { XMP_Assert ( (kTIFF_ByteType <= tagInfo.type) && (tagInfo.type <= kTIFF_LastType) ); XMP_Assert ( mapInfo.type <= kTIFF_LastType ); @@ -1189,7 +1159,9 @@ ImportTIFF_CheckStandardMapping ( const TIFF_Manager::TagInfo & tagInfo, if ( (tagInfo.type != kTIFF_ShortType) && (tagInfo.type != kTIFF_LongType) ) return false; } - if ( (tagInfo.count != mapInfo.count) && (mapInfo.count != kAnyCount) ) return false; + if ( (tagInfo.count != mapInfo.count) && // Maybe there is a problem because the counts don't match. + // (mapInfo.count != kAnyCount) && ... don't need this because of the new check below ... + (mapInfo.count == 1) ) return false; // Be tolerant of mismatch in expected array size. return true; @@ -1200,7 +1172,7 @@ ImportTIFF_CheckStandardMapping ( const TIFF_Manager::TagInfo & tagInfo, // =========================== static void -ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState ) +ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta * xmp ) { const bool nativeEndian = tiff.IsNativeEndian(); TIFF_Manager::TagInfo tagInfo; @@ -1227,22 +1199,16 @@ ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta const TIFF_MappingToXMP & mapInfo = mappings[i]; const bool mapSingle = ((mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType)); - - // Skip tags that have special mappings, they are handled individually later. Delete any - // existing XMP property before going further. But after the special mapping check since we - // don't have the XMP property name for those. This lets legacy deletions propagate and - // eliminates any problems with existing XMP property form. Make sure the actual tag has - // the expected type and count, ignore it (pretend it is not present) if not. - - if ( mapInfo.name[0] == 0 ) continue; // Skip special mappings. - - bool ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, ifd, mapInfo.id, xmpNS, mapInfo.name, &tagInfo ); - if (! ok ) continue; - - XMP_Assert ( tagInfo.type != kTIFF_UndefinedType ); // These have a special mapping. + + if ( mapInfo.name[0] == 0 ) continue; // Skip special mappings, handled higher up. + + bool found = tiff.GetTag ( ifd, mapInfo.id, &tagInfo ); + if ( ! found ) continue; + + XMP_Assert ( tagInfo.type != kTIFF_UndefinedType ); // These must have a special mapping. if ( tagInfo.type == kTIFF_UndefinedType ) continue; if ( ! ImportTIFF_CheckStandardMapping ( tagInfo, mapInfo ) ) continue; - + if ( mapSingle ) { ImportSingleTIFF ( tagInfo, nativeEndian, xmp, xmpNS, mapInfo.name ); } else { @@ -1250,10 +1216,10 @@ ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta } } catch ( ... ) { - + // Do nothing, let other imports proceed. // ? Notify client? - + } } @@ -1274,38 +1240,55 @@ ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta // part, the digits that would be to the right of the decimal point. static void -ImportTIFF_Date ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & dateInfo, XMP_Uns16 secID, +ImportTIFF_Date ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & dateInfo, SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) { + XMP_Uns16 secID; + switch ( dateInfo.id ) { + case kTIFF_DateTime : secID = kTIFF_SubSecTime; break; + case kTIFF_DateTimeOriginal : secID = kTIFF_SubSecTimeOriginal; break; + case kTIFF_DateTimeDigitized : secID = kTIFF_SubSecTimeDigitized; break; + } + try { // Don't let errors with one stop the others. + + if ( (dateInfo.type != kTIFF_ASCIIType) || (dateInfo.count != 20) ) return; const char * dateStr = (const char *) dateInfo.dataPtr; if ( (dateStr[4] != ':') || (dateStr[7] != ':') || (dateStr[10] != ' ') || (dateStr[13] != ':') || (dateStr[16] != ':') ) return; - + XMP_DateTime binValue; - + binValue.year = GatherInt ( &dateStr[0], 4 ); binValue.month = GatherInt ( &dateStr[5], 2 ); binValue.day = GatherInt ( &dateStr[8], 2 ); + if ( (binValue.year != 0) | (binValue.month != 0) | (binValue.day != 0) ) binValue.hasDate = true; + binValue.hour = GatherInt ( &dateStr[11], 2 ); binValue.minute = GatherInt ( &dateStr[14], 2 ); binValue.second = GatherInt ( &dateStr[17], 2 ); binValue.nanoSecond = 0; // Get the fractional seconds later. - binValue.tzSign = binValue.tzHour = binValue.tzMinute = 0; - SXMPUtils::SetTimeZone ( &binValue ); // Assume local time. - + if ( (binValue.hour != 0) | (binValue.minute != 0) | (binValue.second != 0) ) binValue.hasTime = true; + + binValue.tzSign = 0; // ! Separate assignment, avoid VS integer truncation warning. + binValue.tzHour = binValue.tzMinute = 0; + binValue.hasTimeZone = false; // Exif times have no zone. + + // *** Consider looking at the TIFF/EP TimeZoneOffset tag? + TIFF_Manager::TagInfo secInfo; - bool found = tiff.GetTag ( kTIFF_ExifIFD, secID, &secInfo ); - + bool found = tiff.GetTag ( kTIFF_ExifIFD, secID, &secInfo ); // ! Subseconds are all in the Exif IFD. + if ( found && (secInfo.type == kTIFF_ASCIIType) ) { const char * fracPtr = (const char *) secInfo.dataPtr; binValue.nanoSecond = GatherInt ( fracPtr, secInfo.dataLen ); size_t digits = 0; for ( ; (('0' <= *fracPtr) && (*fracPtr <= '9')); ++fracPtr ) ++digits; for ( ; digits < 9; ++digits ) binValue.nanoSecond *= 10; + if ( binValue.nanoSecond != 0 ) binValue.hasTime = true; } - + xmp->SetProperty_Date ( xmpNS, xmpProp, binValue ); } catch ( ... ) { @@ -1326,14 +1309,14 @@ ImportTIFF_LocTextASCII ( const TIFF_Manager & tiff, XMP_Uns8 ifd, XMP_Uns16 tag try { // Don't let errors with one stop the others. TIFF_Manager::TagInfo tagInfo; - + bool found = tiff.GetTag ( ifd, tagID, &tagInfo ); if ( (! found) || (tagInfo.type != kTIFF_ASCIIType) ) return; - + const char * chPtr = (const char *)tagInfo.dataPtr; const bool hasNul = (chPtr[tagInfo.dataLen-1] == 0); const bool isUTF8 = ReconcileUtils::IsUTF8 ( chPtr, tagInfo.dataLen ); - + if ( isUTF8 && hasNul ) { xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", chPtr ); } else { @@ -1341,11 +1324,8 @@ ImportTIFF_LocTextASCII ( const TIFF_Manager & tiff, XMP_Uns8 ifd, XMP_Uns16 tag if ( isUTF8 ) { strValue.assign ( chPtr, tagInfo.dataLen ); } else { - #if ! XMP_UNIXBuild - ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); - #else - return; // ! Hack until legacy-as-local issues are resolved for generic UNIX. - #endif + if ( ignoreLocalText ) return; + ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); } xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", strValue.c_str() ); } @@ -1368,7 +1348,7 @@ ImportTIFF_EncodedString ( const TIFF_Manager & tiff, const TIFF_Manager::TagInf try { // Don't let errors with one stop the others. std::string strValue; - + bool ok = tiff.DecodeString ( tagInfo.dataPtr, tagInfo.dataLen, &strValue ); if ( ! ok ) return; @@ -1382,7 +1362,7 @@ ImportTIFF_EncodedString ( const TIFF_Manager & tiff, const TIFF_Manager::TagInf // Do nothing, let other imports proceed. // ? Notify client? } - + } // ImportTIFF_EncodedString // ================================================================================================= @@ -1397,15 +1377,15 @@ ImportTIFF_Flash ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, XMP_Uns16 binValue = *((XMP_Uns16*)tagInfo.dataPtr); if ( ! nativeEndian ) binValue = Flip2 ( binValue ); - + bool fired = (bool)(binValue & 1); // Avoid implicit 0/1 conversion. int rtrn = (binValue >> 1) & 3; int mode = (binValue >> 3) & 3; bool function = (bool)((binValue >> 5) & 1); bool redEye = (bool)((binValue >> 6) & 1); - + static const char * sTwoBits[] = { "0", "1", "2", "3" }; - + xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Fired", (fired ? kXMP_TrueStr : kXMP_FalseStr) ); xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Return", sTwoBits[rtrn] ); xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Mode", sTwoBits[mode] ); @@ -1422,7 +1402,7 @@ ImportTIFF_Flash ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, // ================================================================================================= // ImportTIFF_OECFTable // ==================== -// +// // Although the XMP for the OECF and SFR tables is the same, the Exif is not. The OECF table has // signed rational values and the SFR table has unsigned. @@ -1434,25 +1414,25 @@ ImportTIFF_OECFTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; - - XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); if ( ! nativeEndian ) { columns = Flip2 ( columns ); rows = Flip2 ( rows ); } - + char buffer[40]; - + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); - + std::string arrayPath; - + SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Names", &arrayPath ); - + bytePtr += 4; // Move to the list of names. for ( size_t i = columns; i > 0; --i ) { size_t nameLen = strlen((XMP_StringPtr)bytePtr) + 1; // ! Include the terminating nul. @@ -1460,26 +1440,26 @@ ImportTIFF_OECFTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, (XMP_StringPtr)bytePtr ); bytePtr += nameLen; } - + if ( (byteEnd - bytePtr) != (8 * columns * rows) ) { xmp->DeleteProperty ( xmpNS, xmpProp ); return; }; // Make sure the values are present. SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Values", &arrayPath ); - + XMP_Int32 * binPtr = (XMP_Int32*)bytePtr; for ( size_t i = (columns * rows); i > 0; --i, binPtr += 2 ) { - + XMP_Int32 binNum = binPtr[0]; XMP_Int32 binDenom = binPtr[1]; if ( ! nativeEndian ) { Flip4 ( &binNum ); Flip4 ( &binDenom ); } - - snprintf ( buffer, sizeof(buffer), "%ld/%ld", binNum, binDenom ); // AUDIT: Use of sizeof(buffer) is safe. - + + snprintf ( buffer, sizeof(buffer), "%ld/%ld", (long)binNum, (long)binDenom ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, buffer ); - + } - + return; } catch ( ... ) { @@ -1492,7 +1472,7 @@ ImportTIFF_OECFTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, // ================================================================================================= // ImportTIFF_SFRTable // =================== -// +// // Although the XMP for the OECF and SFR tables is the same, the Exif is not. The OECF table has // signed rational values and the SFR table has unsigned. @@ -1504,25 +1484,25 @@ ImportTIFF_SFRTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; - - XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); if ( ! nativeEndian ) { columns = Flip2 ( columns ); rows = Flip2 ( rows ); } - + char buffer[40]; - + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); - + std::string arrayPath; - + SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Names", &arrayPath ); - + bytePtr += 4; // Move to the list of names. for ( size_t i = columns; i > 0; --i ) { size_t nameLen = strlen((XMP_StringPtr)bytePtr) + 1; // ! Include the terminating nul. @@ -1530,26 +1510,26 @@ ImportTIFF_SFRTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, (XMP_StringPtr)bytePtr ); bytePtr += nameLen; } - + if ( (byteEnd - bytePtr) != (8 * columns * rows) ) { xmp->DeleteProperty ( xmpNS, xmpProp ); return; }; // Make sure the values are present. SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Values", &arrayPath ); - + XMP_Uns32 * binPtr = (XMP_Uns32*)bytePtr; for ( size_t i = (columns * rows); i > 0; --i, binPtr += 2 ) { - + XMP_Uns32 binNum = binPtr[0]; XMP_Uns32 binDenom = binPtr[1]; if ( ! nativeEndian ) { binNum = Flip4 ( binNum ); binDenom = Flip4 ( binDenom ); } - - snprintf ( buffer, sizeof(buffer), "%lu/%lu", binNum, binDenom ); // AUDIT: Use of sizeof(buffer) is safe. - + + snprintf ( buffer, sizeof(buffer), "%lu/%lu", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, buffer ); - + } - + return; } catch ( ... ) { @@ -1571,34 +1551,34 @@ ImportTIFF_CFATable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; - - XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); if ( ! nativeEndian ) { columns = Flip2 ( columns ); rows = Flip2 ( rows ); } - + char buffer[20]; std::string arrayPath; - + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); - + bytePtr += 4; // Move to the matrix of values. if ( (byteEnd - bytePtr) != (columns * rows) ) goto BadExif; // Make sure the values are present. - + SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Values", &arrayPath ); - + for ( size_t i = (columns * rows); i > 0; --i, ++bytePtr ) { snprintf ( buffer, sizeof(buffer), "%hu", *bytePtr ); // AUDIT: Use of sizeof(buffer) is safe. xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, buffer ); } - + return; - + BadExif: // Ignore the tag if the table is ill-formed. xmp->DeleteProperty ( xmpNS, xmpProp ); return; @@ -1622,55 +1602,55 @@ ImportTIFF_DSDTable ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & t const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; - - XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); if ( ! tiff.IsNativeEndian() ) { columns = Flip2 ( columns ); rows = Flip2 ( rows ); } - + char buffer[20]; - + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); - + std::string arrayPath; SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Settings", &arrayPath ); - + bytePtr += 4; // Move to the list of settings. UTF16Unit * utf16Ptr = (UTF16Unit*)bytePtr; UTF16Unit * utf16End = (UTF16Unit*)byteEnd; - + std::string utf8; - + // Figure 17 in the Exif 2.2 spec is unclear. It has counts for rows and columns, but the // settings are listed as 1..n, not as a rectangular matrix. So, ignore the counts and copy // strings until the end of the Exif value. - + while ( utf16Ptr < utf16End ) { - + size_t nameLen = 0; while ( utf16Ptr[nameLen] != 0 ) ++nameLen; ++nameLen; // ! Include the terminating nul. if ( (utf16Ptr + nameLen) > utf16End ) goto BadExif; - + try { FromUTF16 ( utf16Ptr, nameLen, &utf8, tiff.IsBigEndian() ); } catch ( ... ) { goto BadExif; // Ignore the tag if there are conversion errors. } - + xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, utf8.c_str() ); - + utf16Ptr += nameLen; - + } - + return; - + BadExif: // Ignore the tag if the table is ill-formed. xmp->DeleteProperty ( xmpNS, xmpProp ); return; @@ -1693,13 +1673,13 @@ ImportTIFF_GPSCoordinate ( const TIFF_Manager & tiff, const TIFF_Manager::TagInf try { // Don't let errors with one stop the others. const bool nativeEndian = tiff.IsNativeEndian(); - + XMP_Uns16 refID = posInfo.id - 1; // ! The GPS refs and coordinates are all tag n and n+1. TIFF_Manager::TagInfo refInfo; bool found = tiff.GetTag ( kTIFF_GPSInfoIFD, refID, &refInfo ); if ( (! found) || (refInfo.type != kTIFF_ASCIIType) || (refInfo.count != 2) ) return; char ref = *((char*)refInfo.dataPtr); - + XMP_Uns32 * binPtr = (XMP_Uns32*)posInfo.dataPtr; XMP_Uns32 degNum = binPtr[0]; XMP_Uns32 degDenom = binPtr[1]; @@ -1715,32 +1695,32 @@ ImportTIFF_GPSCoordinate ( const TIFF_Manager & tiff, const TIFF_Manager::TagInf secNum = Flip4 ( secNum ); secDenom = Flip4 ( secDenom ); } - + char buffer[40]; - + if ( (degDenom == 1) && (minDenom == 1) && (secDenom == 1) ) { - - snprintf ( buffer, sizeof(buffer), "%lu,%lu,%lu%c", degNum, minNum, secNum, ref ); // AUDIT: Using sizeof(buffer is safe. - + + snprintf ( buffer, sizeof(buffer), "%lu,%lu,%lu%c", (unsigned long)degNum, (unsigned long)minNum, (unsigned long)secNum, ref ); // AUDIT: Using sizeof(buffer is safe. + } else { - + XMP_Uns32 maxDenom = degDenom; if ( minDenom > degDenom ) maxDenom = minDenom; if ( secDenom > degDenom ) maxDenom = secDenom; - + int fracDigits = 1; while ( maxDenom > 10 ) { ++fracDigits; maxDenom = maxDenom/10; } - + double temp = (double)degNum / (double)degDenom; double degrees = (double)((XMP_Uns32)temp); // Just the integral number of degrees. double minutes = ((temp - degrees) * 60.0) + ((double)minNum / (double)minDenom) + (((double)secNum / (double)secDenom) / 60.0); - + snprintf ( buffer, sizeof(buffer), "%.0f,%.*f%c", degrees, fracDigits, minutes, ref ); // AUDIT: Using sizeof(buffer is safe. - + } - + xmp->SetProperty ( xmpNS, xmpProp, buffer ); } catch ( ... ) { @@ -1761,18 +1741,18 @@ ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo try { // Don't let errors with one stop the others. const bool nativeEndian = tiff.IsNativeEndian(); - + bool haveDate; TIFF_Manager::TagInfo dateInfo; haveDate = tiff.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDateStamp, &dateInfo ); if ( ! haveDate ) haveDate = tiff.GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeOriginal, &dateInfo ); if ( ! haveDate ) haveDate = tiff.GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeDigitized, &dateInfo ); if ( ! haveDate ) return; - + const char * dateStr = (const char *) dateInfo.dataPtr; if ( (dateStr[4] != ':') || (dateStr[7] != ':') ) return; if ( (dateStr[10] != 0) && (dateStr[10] != ' ') ) return; - + XMP_Uns32 * binPtr = (XMP_Uns32*)timeInfo.dataPtr; XMP_Uns32 hourNum = binPtr[0]; XMP_Uns32 hourDenom = binPtr[1]; @@ -1788,8 +1768,8 @@ ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo secNum = Flip4 ( secNum ); secDenom = Flip4 ( secDenom ); } - - double fHour, fMin, fSec, fNano, temp; + + double fHour, fMin, fSec, fNano, temp; fSec = (double)secNum / (double)secDenom; temp = (double)minNum / (double)minDenom; fMin = (double)((XMP_Uns32)temp); @@ -1798,20 +1778,21 @@ ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo fHour = (double)((XMP_Uns32)temp); fSec += (temp - fHour) * 3600.0; temp = (double)((XMP_Uns32)fSec); - fNano = (fSec - temp) * (1000.0*1000.0*1000.0); + fNano = ((fSec - temp) * (1000.0*1000.0*1000.0)) + 0.5; // Try to avoid n999... problems. fSec = temp; - + XMP_DateTime binStamp; - binStamp.tzSign = kXMP_TimeIsUTC; - binStamp.tzHour = binStamp.tzMinute = 0; binStamp.year = GatherInt ( dateStr, 4 ); binStamp.month = GatherInt ( dateStr+5, 2 ); binStamp.day = GatherInt ( dateStr+8, 2 ); binStamp.hour = (XMP_Int32)fHour; binStamp.minute = (XMP_Int32)fMin; binStamp.second = (XMP_Int32)fSec; - binStamp.nanoSecond = (XMP_Int32)fNano; - + binStamp.nanoSecond = (XMP_Int32)fNano; + binStamp.hasTimeZone = true; // Exif GPS TimeStamp is implicitly UTC. + binStamp.tzSign = kXMP_TimeIsUTC; + binStamp.tzHour = binStamp.tzMinute = 0; + xmp->SetProperty_Date ( xmpNS, xmpProp, binStamp ); } catch ( ... ) { @@ -1825,76 +1806,99 @@ ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo // ================================================================================================= // ================================================================================================= -// ReconcileUtils::ImportTIFF -// ========================== +// PhotoDataUtils::Import2WayExif +// ============================== +// +// Import the TIFF/Exif tags that have 2 way mappings to XMP, i.e. no correspondence to IPTC. +// These are always imported for the tiff: and exif: namespaces, but not for others. void -ReconcileUtils::ImportTIFF ( const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState, XMP_FileFormat srcFormat ) +PhotoDataUtils::Import2WayExif ( const TIFF_Manager & exif, SXMPMeta * xmp, int iptcDigestState ) { + const bool nativeEndian = exif.IsNativeEndian(); + + bool found, foundFromXMP; TIFF_Manager::TagInfo tagInfo; - bool ok; - ImportTIFF_StandardMappings ( kTIFF_PrimaryIFD, tiff, xmp, digestState ); + ImportTIFF_StandardMappings ( kTIFF_PrimaryIFD, exif, xmp ); + ImportTIFF_StandardMappings ( kTIFF_ExifIFD, exif, xmp ); + ImportTIFF_StandardMappings ( kTIFF_GPSInfoIFD, exif, xmp ); + + // ----------------------------------------------------------------- + // Fixup erroneous files that have a negative value for GPSAltitude. - // 306 DateTime is a date master with 37520 SubSecTime and is mapped to xmp:ModifyDate. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_PrimaryIFD, kTIFF_DateTime, - kXMP_NS_XMP, "ModifyDate", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count == 20) ) { - ImportTIFF_Date ( tiff, tagInfo, kTIFF_SubSecTime, xmp, kXMP_NS_XMP, "ModifyDate" ); - } + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAltitude, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_RationalType) && (tagInfo.count == 1) ) { - if ( srcFormat != kXMP_PhotoshopFile ) { - - // ! TIFF tags 270, 315, and 33432 are ignored for Photoshop files. - - XMP_Assert ( (srcFormat == kXMP_JPEGFile) || (srcFormat == kXMP_TIFFFile) ); - - // 270 ImageDescription is an ASCII tag and is mapped to dc:description["x-default"]. - // Call ImportTIFF_VerifyImport using the x-default item path, don't delete the whole array. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_PrimaryIFD, kTIFF_ImageDescription, - kXMP_NS_DC, "description[?xml:lang='x-default']", &tagInfo ); - if ( ok ) ImportTIFF_LocTextASCII ( tiff, kTIFF_PrimaryIFD, kTIFF_ImageDescription, - xmp, kXMP_NS_DC, "description" ); - - // 315 Artist is an ASCII tag and is mapped to dc:creator[*]. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_PrimaryIFD, kTIFF_Artist, - kXMP_NS_DC, "creator", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ASCIIType) ) { - ImportArrayTIFF_ASCII ( tagInfo, xmp, kXMP_NS_DC, "creator" ); + XMP_Uns32 num = exif.GetUns32 ( tagInfo.dataPtr ); + XMP_Uns32 denom = exif.GetUns32 ( (XMP_Uns8*)tagInfo.dataPtr + 4 ); + bool numNeg = num >> 31; + bool denomNeg = denom >> 31; + + if ( (numNeg != denomNeg) || numNeg ) { // Does the GPSAltitude look negative? + if ( denomNeg ) { + denom = -denom; + num = -num; + numNeg = num >> 31; + } + if ( numNeg ) { + char buffer [32]; + num = -num; + snprintf ( buffer, sizeof(buffer), "%lu/%lu", (unsigned long) num, (unsigned long) denom ); // AUDIT: Using sizeof(buffer) is safe. + xmp->SetProperty ( kXMP_NS_EXIF, "GPSAltitude", buffer ); + xmp->SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "1" ); + } } - // 33432 Copyright is mapped to dc:rights["x-default"]. - // Call ImportTIFF_VerifyImport using the x-default item path, don't delete the whole array. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_PrimaryIFD, kTIFF_Copyright, - kXMP_NS_DC, "rights[?xml:lang='x-default']", &tagInfo ); - if ( ok ) ImportTIFF_LocTextASCII ( tiff, kTIFF_PrimaryIFD, kTIFF_Copyright, xmp, kXMP_NS_DC, "rights" ); - } + + // --------------------------------------------------------------- + // Import DateTimeOriginal and DateTime if the XMP doss not exist. -} // ReconcileUtils::ImportTIFF; + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeOriginal, &tagInfo ); + foundFromXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "DateTimeOriginal" ); + + if ( found && (! foundFromXMP) && (tagInfo.type == kTIFF_ASCIIType) ) { + ImportTIFF_Date ( exif, tagInfo, xmp, kXMP_NS_EXIF, "DateTimeOriginal" ); + } -// ================================================================================================= -// ReconcileUtils::ImportExif -// ========================== + found = exif.GetTag ( kTIFF_PrimaryIFD, kTIFF_DateTime, &tagInfo ); + foundFromXMP = xmp->DoesPropertyExist ( kXMP_NS_XMP, "ModifyDate" ); + + if ( found && (! foundFromXMP) && (tagInfo.type == kTIFF_ASCIIType) ) { + ImportTIFF_Date ( exif, tagInfo, xmp, kXMP_NS_XMP, "ModifyDate" ); + } -void -ReconcileUtils::ImportExif ( const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState ) -{ - const bool nativeEndian = tiff.IsNativeEndian(); + // ---------------------------------------------------- + // Import the Exif IFD tags that have special mappings. + + // 34855 ISOSpeedRatings has special cases for ISO over 65535. The tag is SHORT, some cameras + // omit the tag and some write 65535, all put the real ISO in MakerNote - which ACR might + // extract and leave in the XMP. There are 3 import cases: + // 1. No native tag: Leave existing XMP. + // 2. All of the native values are under 65535: Clear any XMP and import. + // 3. One or more of the native values are 65535: Leave existing XMP, else import. + + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeedRatings, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ShortType) && (tagInfo.count > 0) ) { - TIFF_Manager::TagInfo tagInfo; - bool ok; + bool keepXMP = false; + XMP_Uns16 * itemPtr = (XMP_Uns16*) tagInfo.dataPtr; + for ( XMP_Uns32 i = 0; i < tagInfo.count; ++i, ++itemPtr ) { + if ( *itemPtr == 0xFFFF ) { keepXMP = true; break; } // ! Don't care about BE or LF, same either way. + } - ImportTIFF_StandardMappings ( kTIFF_ExifIFD, tiff, xmp, digestState ); - ImportTIFF_StandardMappings ( kTIFF_GPSInfoIFD, tiff, xmp, digestState ); + if ( ! keepXMP ) xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + + if ( ! xmp->DoesPropertyExist ( kXMP_NS_EXIF, "ISOSpeedRatings" ) ) { + ImportArrayTIFF ( tagInfo, exif.IsNativeEndian(), xmp, kXMP_NS_EXIF, "ISOSpeedRatings" ); + } - // ------------------------------------------------------ - // Here are the Exif IFD tags that have special mappings: + } // 36864 ExifVersion is 4 "undefined" ASCII characters. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_ExifVersion, - kXMP_NS_EXIF, "ExifVersion", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { char str[5]; *((XMP_Uns32*)str) = *((XMP_Uns32*)tagInfo.dataPtr); str[4] = 0; @@ -1902,9 +1906,8 @@ ReconcileUtils::ImportExif ( const TIFF_Manager & tiff, SXMPMeta * xmp, int dige } // 40960 FlashpixVersion is 4 "undefined" ASCII characters. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_FlashpixVersion, - kXMP_NS_EXIF, "FlashpixVersion", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_FlashpixVersion, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { char str[5]; *((XMP_Uns32*)str) = *((XMP_Uns32*)tagInfo.dataPtr); str[4] = 0; @@ -1912,94 +1915,65 @@ ReconcileUtils::ImportExif ( const TIFF_Manager & tiff, SXMPMeta * xmp, int dige } // 37121 ComponentsConfiguration is an array of 4 "undefined" UInt8 bytes. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, - kXMP_NS_EXIF, "ComponentsConfiguration", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { ImportArrayTIFF_Byte ( tagInfo, xmp, kXMP_NS_EXIF, "ComponentsConfiguration" ); } // 37510 UserComment is a string with explicit encoding. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_UserComment, - kXMP_NS_EXIF, "UserComment", &tagInfo ); - if ( ok ) { - ImportTIFF_EncodedString ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "UserComment", true /* isLangAlt */ ); - } - - // 36867 DateTimeOriginal is a date master with 37521 SubSecTimeOriginal. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_DateTimeOriginal, - kXMP_NS_EXIF, "DateTimeOriginal", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count == 20) ) { - ImportTIFF_Date ( tiff, tagInfo, kTIFF_SubSecTimeOriginal, xmp, kXMP_NS_EXIF, "DateTimeOriginal" ); - } - if ( ! xmp->DoesPropertyExist ( kXMP_NS_XMP, "CreateDate" ) ) { - std::string exifDate; - ok = xmp->GetProperty ( kXMP_NS_EXIF, "DateTimeOriginal", &exifDate, 0 ); - if ( ok ) xmp->SetProperty ( kXMP_NS_XMP, "CreateDate", exifDate.c_str() ); - } - - // 36868 DateTimeDigitized is a date master with 37522 SubSecTimeDigitized. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_DateTimeDigitized, - kXMP_NS_EXIF, "DateTimeDigitized", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count == 20) ) { - ImportTIFF_Date ( tiff, tagInfo, kTIFF_SubSecTimeDigitized, xmp, kXMP_NS_EXIF, "DateTimeDigitized" ); + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_UserComment, &tagInfo ); + if ( found ) { + ImportTIFF_EncodedString ( exif, tagInfo, xmp, kXMP_NS_EXIF, "UserComment", true /* isLangAlt */ ); } // 34856 OECF is an OECF/SFR table. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_OECF, - kXMP_NS_EXIF, "OECF", &tagInfo ); - if ( ok ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_OECF, &tagInfo ); + if ( found ) { ImportTIFF_OECFTable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "OECF" ); } // 37385 Flash is a UInt16 collection of bit fields and is mapped to a struct in XMP. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_Flash, - kXMP_NS_EXIF, "Flash", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ShortType) && (tagInfo.count == 1) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_Flash, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ShortType) && (tagInfo.count == 1) ) { ImportTIFF_Flash ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "Flash" ); } // 41484 SpatialFrequencyResponse is an OECF/SFR table. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_SpatialFrequencyResponse, - kXMP_NS_EXIF, "SpatialFrequencyResponse", &tagInfo ); - if ( ok ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_SpatialFrequencyResponse, &tagInfo ); + if ( found ) { ImportTIFF_SFRTable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "SpatialFrequencyResponse" ); } // 41728 FileSource is an "undefined" UInt8. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_FileSource, - kXMP_NS_EXIF, "FileSource", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 1) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_FileSource, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 1) ) { ImportSingleTIFF_Byte ( tagInfo, xmp, kXMP_NS_EXIF, "FileSource" ); } // 41729 SceneType is an "undefined" UInt8. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_SceneType, - kXMP_NS_EXIF, "SceneType", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 1) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_SceneType, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 1) ) { ImportSingleTIFF_Byte ( tagInfo, xmp, kXMP_NS_EXIF, "SceneType" ); } // 41730 CFAPattern is a custom table. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_CFAPattern, - kXMP_NS_EXIF, "CFAPattern", &tagInfo ); - if ( ok ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_CFAPattern, &tagInfo ); + if ( found ) { ImportTIFF_CFATable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "CFAPattern" ); } // 41995 DeviceSettingDescription is a custom table. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_ExifIFD, kTIFF_DeviceSettingDescription, - kXMP_NS_EXIF, "DeviceSettingDescription", &tagInfo ); - if ( ok ) { - ImportTIFF_DSDTable ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "DeviceSettingDescription" ); + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_DeviceSettingDescription, &tagInfo ); + if ( found ) { + ImportTIFF_DSDTable ( exif, tagInfo, xmp, kXMP_NS_EXIF, "DeviceSettingDescription" ); } - // ---------------------------------------------------------- - // Here are the GPS Info IFD tags that have special mappings: + // -------------------------------------------------------- + // Import the GPS Info IFD tags that have special mappings. // 0 GPSVersionID is 4 UInt8 bytes and mapped as "n.n.n.n". - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, - kXMP_NS_EXIF, "GPSVersionID", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_ByteType) && (tagInfo.count == 4) ) { + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ByteType) && (tagInfo.count == 4) ) { const char * strIn = (const char *) tagInfo.dataPtr; char strOut[8]; strOut[0] = strIn[0]; @@ -2012,213 +1986,418 @@ ReconcileUtils::ImportExif ( const TIFF_Manager & tiff, SXMPMeta * xmp, int dige } // 2 GPSLatitude is a GPS coordinate master. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSLatitude, - kXMP_NS_EXIF, "GPSLatitude", &tagInfo ); - if ( ok ) { - ImportTIFF_GPSCoordinate ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSLatitude" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSLatitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSLatitude" ); } // 4 GPSLongitude is a GPS coordinate master. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSLongitude, - kXMP_NS_EXIF, "GPSLongitude", &tagInfo ); - if ( ok ) { - ImportTIFF_GPSCoordinate ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSLongitude" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSLongitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSLongitude" ); } // 7 GPSTimeStamp is a UTC time as 3 rationals, mated with the optional GPSDateStamp. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, - kXMP_NS_EXIF, "GPSTimeStamp", &tagInfo ); - if ( ok && (tagInfo.type == kTIFF_RationalType) && (tagInfo.count == 3) ) { - ImportTIFF_GPSTimeStamp ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSTimeStamp" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_RationalType) && (tagInfo.count == 3) ) { + ImportTIFF_GPSTimeStamp ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSTimeStamp" ); } // 20 GPSDestLatitude is a GPS coordinate master. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude, - kXMP_NS_EXIF, "GPSDestLatitude", &tagInfo ); - if ( ok ) { - ImportTIFF_GPSCoordinate ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSDestLatitude" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSDestLatitude" ); } // 22 GPSDestLongitude is a GPS coordinate master. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude, - kXMP_NS_EXIF, "GPSDestLongitude", &tagInfo ); - if ( ok ) { - ImportTIFF_GPSCoordinate ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSDestLongitude" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSDestLongitude" ); } // 27 GPSProcessingMethod is a string with explicit encoding. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod, - kXMP_NS_EXIF, "GPSProcessingMethod", &tagInfo ); - if ( ok ) { - ImportTIFF_EncodedString ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSProcessingMethod" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod, &tagInfo ); + if ( found ) { + ImportTIFF_EncodedString ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSProcessingMethod" ); } // 28 GPSAreaInformation is a string with explicit encoding. - ok = ImportTIFF_VerifyImport ( tiff, xmp, digestState, kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation, - kXMP_NS_EXIF, "GPSAreaInformation", &tagInfo ); - if ( ok ) { - ImportTIFF_EncodedString ( tiff, tagInfo, xmp, kXMP_NS_EXIF, "GPSAreaInformation" ); + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation, &tagInfo ); + if ( found ) { + ImportTIFF_EncodedString ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSAreaInformation" ); } -} // ReconcileUtils::ImportExif; +} // PhotoDataUtils::Import2WayExif // ================================================================================================= -// ================================================================================================= +// Import3WayDateTime +// ================== + +static void Import3WayDateTime ( XMP_Uns16 exifTag, const TIFF_Manager & exif, const IPTC_Manager & iptc, + SXMPMeta * xmp, int iptcDigestState, const IPTC_Manager & oldIPTC ) +{ + XMP_Uns8 iptcDS; + XMP_StringPtr xmpNS, xmpProp; + + if ( exifTag == kTIFF_DateTimeOriginal ) { + iptcDS = kIPTC_DateCreated; + xmpNS = kXMP_NS_Photoshop; + xmpProp = "DateCreated"; + } else if ( exifTag == kTIFF_DateTimeDigitized ) { + iptcDS = kIPTC_DigitalCreateDate; + xmpNS = kXMP_NS_XMP; + xmpProp = "CreateDate"; + } else { + XMP_Throw ( "Unrecognized dateID", kXMPErr_BadParam ); + } + + size_t iptcCount; + bool haveXMP, haveExif, haveIPTC; // ! These are manipulated to simplify MWG-compliant logic. + std::string xmpValue, exifValue, iptcValue; + TIFF_Manager::TagInfo exifInfo; + IPTC_Manager::DataSetInfo iptcInfo; + + // Get the basic info about available values. + haveXMP = xmp->GetProperty ( xmpNS, xmpProp, &xmpValue, 0 ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, iptcDS, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_ExifIFD, exifTag, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC ) { + + PhotoDataUtils::ImportIPTC_Date ( iptcDS, iptc, xmp ); + + } else if ( haveExif && (exifInfo.type == kTIFF_ASCIIType) ) { + + // Only import the Exif form if the non-TZ information differs from the XMP. + + TIFF_FileWriter exifFromXMP; + TIFF_Manager::TagInfo infoFromXMP; + + ExportTIFF_Date ( *xmp, xmpNS, xmpProp, &exifFromXMP, exifTag ); + bool foundFromXMP = exifFromXMP.GetTag ( kTIFF_ExifIFD, exifTag, &infoFromXMP ); + + if ( (! foundFromXMP) || (exifInfo.dataLen != infoFromXMP.dataLen) || + (! XMP_LitNMatch ( (char*)exifInfo.dataPtr, (char*)infoFromXMP.dataPtr, exifInfo.dataLen )) ) { + ImportTIFF_Date ( exif, exifInfo, xmp, xmpNS, xmpProp ); + } + + } + +} // Import3WayDateTime // ================================================================================================= -// ExportSingleTIFF_Short -// ====================== +// PhotoDataUtils::Import3WayItems +// =============================== +// +// Handle the imports that involve all 3 of Exif, IPTC, and XMP. There are only 4 properties with +// 3-way mappings, copyright, description, creator, and date/time. Following the MWG guidelines, +// this general policy is applied separately to each: +// +// If the new IPTC digest differs from the stored digest (favor IPTC over Exif and XMP) +// If the IPTC value differs from the predicted old IPTC value +// Import the IPTC value, including deleting the XMP +// Else if the Exif is non-empty and differs from the XMP +// Import the Exif value (does not delete existing XMP) +// Else if the stored IPTC digest is missing (favor Exif over IPTC, or IPTC over missing XMP) +// If the Exif is non-empty and differs from the XMP +// Import the Exif value (does not delete existing XMP) +// Else if the XMP is missing and the Exif is missing or empty +// Import the IPTC value +// Else (the new IPTC digest matches the stored digest - ignore the IPTC) +// If the Exif is non-empty and differs from the XMP +// Import the Exif value (does not delete existing XMP) +// +// Note that missing or empty Exif will never cause existing XMP to be deleted. This is a pragmatic +// choice to improve compatibility with pre-MWG software. There are few Exif-only editors for these +// 3-way properties, there are important existing IPTC-only editors. -static void -ExportSingleTIFF_Short ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, - TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +// ------------------------------------------------------------------------------------------------- + +void PhotoDataUtils::Import3WayItems ( const TIFF_Manager & exif, const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ) { - try { // Don't let errors with one stop the others. + size_t iptcCount; - long xmpValue; + bool haveXMP, haveExif, haveIPTC; // ! These are manipulated to simplify MWG-compliant logic. + std::string xmpValue, exifValue, iptcValue; - bool foundXMP = xmp.GetProperty_Int ( xmpNS, xmpProp, &xmpValue, 0 ); - if ( ! foundXMP ) { - tiff->DeleteTag ( ifd, id ); - return; - } + TIFF_Manager::TagInfo exifInfo; + IPTC_Manager::DataSetInfo iptcInfo; - if ( (xmpValue < 0) || (xmpValue > 0xFFFF) ) return; // ? Complain? Peg to limit? Delete the tag? + IPTC_Writer oldIPTC; + if ( iptcDigestState == kDigestDiffers ) { + PhotoDataUtils::ExportIPTC ( *xmp, &oldIPTC ); // Predict old IPTC DataSets based on the existing XMP. + } + + // --------------------------------------------------------------------------------- + // Process the copyright. Replace internal nuls in the Exif to "merge" the portions. + + // Get the basic info about available values. + haveXMP = xmp->GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", 0, &xmpValue, 0 ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, kIPTC_CopyrightNotice, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_Copyright, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); - tiff->SetTag_Short ( ifd, id, (XMP_Uns16)xmpValue ); + if ( haveExif && (exifInfo.dataLen > 1) ) { // Replace internal nul characters with linefeed. + for ( XMP_Uns32 i = 0; i < exifInfo.dataLen-1; ++i ) { + if ( ((char*)exifInfo.dataPtr)[i] == 0 ) ((char*)exifInfo.dataPtr)[i] = 0x0A; + } + } + + if ( haveIPTC ) { + PhotoDataUtils::ImportIPTC_LangAlt ( iptc, xmp, kIPTC_CopyrightNotice, kXMP_NS_DC, "rights" ); + } else if ( haveExif && PhotoDataUtils::IsValueDifferent ( exifInfo, xmpValue, &exifValue ) ) { + xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", exifValue.c_str() ); + } + + // ------------------------ + // Process the description. + + // Get the basic info about available values. + haveXMP = xmp->GetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", 0, &xmpValue, 0 ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, kIPTC_Description, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_ImageDescription, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC ) { + PhotoDataUtils::ImportIPTC_LangAlt ( iptc, xmp, kIPTC_Description, kXMP_NS_DC, "description" ); + } else if ( haveExif && PhotoDataUtils::IsValueDifferent ( exifInfo, xmpValue, &exifValue ) ) { + xmp->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", exifValue.c_str() ); + } - } catch ( ... ) { - // Do nothing, let other exports proceed. - // ? Notify client? + // ------------------------------------------------------------------------------------------- + // Process the creator. The XMP and IPTC are arrays, the Exif is a semicolon separated string. + + // Get the basic info about available values. + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_DC, "creator" ); + haveExif = PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_Artist, &exifInfo ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, kIPTC_Creator, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_Artist, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC ) { + PhotoDataUtils::ImportIPTC_Array ( iptc, xmp, kIPTC_Creator, kXMP_NS_DC, "creator" ); + } else if ( haveExif && PhotoDataUtils::IsValueDifferent ( exifInfo, xmpValue, &exifValue ) ) { + SXMPUtils::SeparateArrayItems ( xmp, kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, exifValue ); } -} // ExportSingleTIFF_Short + // ------------------------------------------------------------------------------ + // Process DateTimeDigitized; DateTimeOriginal and DateTime are 2-way. + // *** Exif DateTimeOriginal <-> XMP exif:DateTimeOriginal + // *** IPTC DateCreated <-> XMP photoshop:DateCreated + // *** Exif DateTimeDigitized <-> IPTC DigitalCreateDate <-> XMP xmp:CreateDate + // *** TIFF DateTime <-> XMP xmp:ModifyDate + + Import3WayDateTime ( kTIFF_DateTimeDigitized, exif, iptc, xmp, iptcDigestState, oldIPTC ); + +} // PhotoDataUtils::Import3WayItems // ================================================================================================= -// ExportSingleTIFF_Rational -// ========================= +// ================================================================================================= + +// ================================================================================================= +// ExportSingleTIFF +// ================ // -// An XMP (unsigned) rational is supposed to be written as a string "num/denom". +// This is only called when the XMP exists and will be exported. And only for standard mappings. + +// ! Only implemented for the types known to be needed. static void -ExportSingleTIFF_Rational ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, - TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +ExportSingleTIFF ( TIFF_Manager * tiff, XMP_Uns8 ifd, const TIFF_MappingToXMP & mapInfo, + bool nativeEndian, const std::string & xmpValue ) { - try { // Don't let errors with one stop the others. + XMP_Assert ( (mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType) ); + XMP_Assert ( mapInfo.name[0] != 0 ); // Must be a standard mapping. + + char nextChar; // Used to make sure sscanf consumes all of the string. + + switch ( mapInfo.type ) { - std::string strValue; - XMP_OptionBits xmpFlags; + case kTIFF_ByteType : { + unsigned short binValue; + int items = sscanf ( xmpValue.c_str(), "%hu%c", &binValue, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 1 ) return; // ? complain? notify client? + tiff->SetTag_Byte ( ifd, mapInfo.id, (XMP_Uns8)binValue ); + break; + } - bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &strValue, &xmpFlags ); - if ( ! foundXMP ) { - tiff->DeleteTag ( ifd, id ); - return; + case kTIFF_ShortType : { + unsigned long binValue; + int items = sscanf ( xmpValue.c_str(), "%lu%c", &binValue, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 1 ) return; // ? complain? notify client? + tiff->SetTag_Short ( ifd, mapInfo.id, (XMP_Uns16)binValue ); + break; } - - if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the tag? - - XMP_Uns32 newNum, newDenom; - const char* partPtr; - size_t partLen; - - partPtr = strValue.c_str(); - for ( partLen = 0; partPtr[partLen] != 0; ++partLen ) { - if ( (partPtr[partLen] < '0') || (partPtr[partLen] > '9') ) break; + + case kTIFF_ShortOrLongType : { + unsigned long binValue; + int items = sscanf ( xmpValue.c_str(), "%lu%c", &binValue, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 1 ) return; // ? complain? notify client? + if ( binValue <= 0xFFFF ) { + tiff->SetTag_Short ( ifd, mapInfo.id, (XMP_Uns16)binValue ); + } else { + tiff->SetTag_Long ( ifd, mapInfo.id, (XMP_Uns32)binValue ); + } + break; } - if ( partLen == 0 ) return; // ? Complain? Delete the tag? - newNum = GatherInt ( partPtr, partLen ); - - if ( partPtr[partLen] == 0 ) { - newDenom = 1; // Tolerate bad XMP that just has the numerator. - } else if ( partPtr[partLen] != '/' ) { - return; // ? Complain? Delete the tag? - } else { - partPtr += partLen+1; - for ( partLen = 0; partPtr[partLen] != 0; ++partLen ) { - if ( (partPtr[partLen] < '0') || (partPtr[partLen] > '9') ) break; + + case kTIFF_RationalType : { // The XMP is formatted as "num/denom". + unsigned long num, denom; + int items = sscanf ( xmpValue.c_str(), "%lu/%lu%c", &num, &denom, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 2 ) { + if ( items != 1 ) return; // ? complain? notify client? + denom = 1; // The XMP was just an integer, assume a denominator of 1. + } + tiff->SetTag_Rational ( ifd, mapInfo.id, (XMP_Uns32)num, (XMP_Uns32)denom ); + break; + } + + case kTIFF_SRationalType : { // The XMP is formatted as "num/denom". + signed long num, denom; + int items = sscanf ( xmpValue.c_str(), "%ld/%ld%c", &num, &denom, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 2 ) { + if ( items != 1 ) return; // ? complain? notify client? + denom = 1; // The XMP was just an integer, assume a denominator of 1. } - if ( (partLen == 0) || (partPtr[partLen] != 0) ) return; // ? Complain? Delete the tag? - newDenom = GatherInt ( partPtr, partLen ); + tiff->SetTag_SRational ( ifd, mapInfo.id, (XMP_Int32)num, (XMP_Int32)denom ); + break; } + + case kTIFF_ASCIIType : + tiff->SetTag ( ifd, mapInfo.id, kTIFF_ASCIIType, (XMP_Uns32)(xmpValue.size()+1), xmpValue.c_str() ); + break; - tiff->SetTag_Rational ( ifd, id, newNum, newDenom ); + default: + XMP_Assert ( false ); // Force a debug assert for unexpected types. - } catch ( ... ) { - // Do nothing, let other exports proceed. - // ? Notify client? } - -} // ExportSingleTIFF_Rational + +} // ExportSingleTIFF // ================================================================================================= -// ExportSingleTIFF_ASCII -// ====================== +// ExportArrayTIFF +// ================ +// +// This is only called when the XMP exists and will be exported. And only for standard mappings. + +// ! Only implemented for the types known to be needed. static void -ExportSingleTIFF_ASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, - TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +ExportArrayTIFF ( TIFF_Manager * tiff, XMP_Uns8 ifd, const TIFF_MappingToXMP & mapInfo, bool nativeEndian, + const SXMPMeta & xmp, const char * xmpNS, const char * xmpArray ) { - try { // Don't let errors with one stop the others. - - std::string xmpValue; - XMP_OptionBits xmpFlags; - - bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &xmpValue, &xmpFlags ); - if ( ! foundXMP ) { - tiff->DeleteTag ( ifd, id ); - return; - } - - if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the tag? - - tiff->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)( xmpValue.size()+1 ), xmpValue.c_str() ); - - } catch ( ... ) { - // Do nothing, let other exports proceed. - // ? Notify client? + XMP_Assert ( (mapInfo.count != 1) && (mapInfo.type != kTIFF_ASCIIType) ); + XMP_Assert ( mapInfo.name[0] != 0 ); // Must be a standard mapping. + XMP_Assert ( mapInfo.type == kTIFF_ShortType ); // ! So far ISOSpeedRatings is the only standard array export. + XMP_Assert ( xmp.DoesPropertyExist ( xmpNS, xmpArray ) ); + + if ( mapInfo.type != kTIFF_ShortType ) return; // ! So far ISOSpeedRatings is the only standard array export. + + size_t arraySize = xmp.CountArrayItems ( xmpNS, xmpArray ); + if ( arraySize == 0 ) { + tiff->DeleteTag ( ifd, mapInfo.id ); + return; } -} // ExportSingleTIFF_ASCII + std::vector<XMP_Uns16> vecValue; + vecValue.assign ( arraySize, 0 ); + XMP_Uns16 * binPtr = (XMP_Uns16*) &vecValue[0]; + + std::string itemPath; + XMP_Int32 int32; + XMP_Uns16 uns16; + for ( size_t i = 1; i <= arraySize; ++i, ++binPtr ) { + SXMPUtils::ComposeArrayItemPath ( xmpNS, xmpArray, (XMP_Index)i, &itemPath ); + xmp.GetProperty_Int ( xmpNS, itemPath.c_str(), &int32, 0 ); + uns16 = (XMP_Uns16)int32; + if ( ! nativeEndian ) uns16 = Flip2 ( uns16 ); + *binPtr = uns16; + } + + tiff->SetTag ( ifd, mapInfo.id, kTIFF_ShortType, (XMP_Uns32)arraySize, &vecValue[0] ); + +} // ExportArrayTIFF // ================================================================================================= -// ExportArrayTIFF_ASCII -// ===================== -// -// Catenate all of the XMP array values into a string with separating nul characters. +// ExportTIFF_StandardMappings +// =========================== static void -ExportArrayTIFF_ASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, - TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +ExportTIFF_StandardMappings ( XMP_Uns8 ifd, TIFF_Manager * tiff, const SXMPMeta & xmp ) { - try { // Don't let errors with one stop the others. + const bool nativeEndian = tiff->IsNativeEndian(); + TIFF_Manager::TagInfo tagInfo; + std::string xmpValue; + XMP_OptionBits xmpForm; - std::string itemValue, fullValue; - XMP_OptionBits xmpFlags; + const TIFF_MappingToXMP * mappings = 0; + const char * xmpNS = 0; - bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); - if ( ! foundXMP ) { - tiff->DeleteTag ( ifd, id ); - return; - } + if ( ifd == kTIFF_PrimaryIFD ) { + mappings = sPrimaryIFDMappings; + xmpNS = kXMP_NS_TIFF; + } else if ( ifd == kTIFF_ExifIFD ) { + mappings = sExifIFDMappings; + xmpNS = kXMP_NS_EXIF; + } else if ( ifd == kTIFF_GPSInfoIFD ) { + mappings = sGPSInfoIFDMappings; + xmpNS = kXMP_NS_EXIF; // ! Yes, the GPS Info tags are in the exif: namespace. + } else { + XMP_Throw ( "Invalid IFD for standard mappings", kXMPErr_InternalFailure ); + } + + for ( size_t i = 0; mappings[i].id != 0xFFFF; ++i ) { + + try { // Don't let errors with one stop the others. + + const TIFF_MappingToXMP & mapInfo = mappings[i]; + + if ( mapInfo.exportMode == kExport_Never ) continue; + if ( mapInfo.name[0] == 0 ) continue; // Skip special mappings, handled higher up. + + bool haveTIFF = tiff->GetTag ( ifd, mapInfo.id, &tagInfo ); + if ( haveTIFF && (mapInfo.exportMode == kExport_InjectOnly) ) continue; + + bool haveXMP = xmp.GetProperty ( xmpNS, mapInfo.name, &xmpValue, &xmpForm ); + if ( ! haveXMP ) { + + if ( haveTIFF && (mapInfo.exportMode == kExport_Always) ) tiff->DeleteTag ( ifd, mapInfo.id ); + + } else { + + XMP_Assert ( tagInfo.type != kTIFF_UndefinedType ); // These must have a special mapping. + if ( tagInfo.type == kTIFF_UndefinedType ) continue; + + const bool mapSingle = ((mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType)); + if ( mapSingle ) { + if ( ! XMP_PropIsSimple ( xmpForm ) ) continue; // ? Notify client? + ExportSingleTIFF ( tiff, ifd, mapInfo, nativeEndian, xmpValue ); + } else { + if ( ! XMP_PropIsArray ( xmpForm ) ) continue; // ? Notify client? + ExportArrayTIFF ( tiff, ifd, mapInfo, nativeEndian, xmp, xmpNS, mapInfo.name ); + } + + } + + } catch ( ... ) { + + // Do nothing, let other imports proceed. + // ? Notify client? - if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the tag? - - size_t count = xmp.CountArrayItems ( xmpNS, xmpProp ); - for ( size_t i = 1; i <= count; ++i ) { // ! XMP arrays are indexed from 1. - (void) xmp.GetArrayItem ( xmpNS, xmpProp, (XMP_Index)i, &itemValue, &xmpFlags ); - if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? - fullValue.append ( itemValue ); - fullValue.append ( 1, '\x0' ); } - - tiff->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)fullValue.size(), fullValue.c_str() ); // ! Already have trailing nul. - } catch ( ... ) { - // Do nothing, let other exports proceed. - // ? Notify client? } - -} // ExportArrayTIFF_ASCII + +} // ExportTIFF_StandardMappings // ================================================================================================= // ExportTIFF_Date @@ -2231,35 +2410,77 @@ ExportArrayTIFF_ASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * x // decimal point. static void -ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, - TIFF_Manager * tiff, XMP_Uns8 mainIFD, XMP_Uns16 mainID, XMP_Uns8 fracIFD, XMP_Uns16 fracID ) +ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, TIFF_Manager * tiff, XMP_Uns16 mainID ) { - try { // Don't let errors with one stop the others. + XMP_Uns8 mainIFD = kTIFF_ExifIFD; + XMP_Uns16 fracID; + switch ( mainID ) { + case kTIFF_DateTime : mainIFD = kTIFF_PrimaryIFD; fracID = kTIFF_SubSecTime; break; + case kTIFF_DateTimeOriginal : fracID = kTIFF_SubSecTimeOriginal; break; + case kTIFF_DateTimeDigitized : fracID = kTIFF_SubSecTimeDigitized; break; + } - XMP_DateTime xmpValue; + try { // Don't let errors with one stop the others. - bool foundXMP = xmp.GetProperty_Date ( xmpNS, xmpProp, &xmpValue, 0 ); + std::string xmpStr; + bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &xmpStr, 0 ); if ( ! foundXMP ) { tiff->DeleteTag ( mainIFD, mainID ); - tiff->DeleteTag ( fracIFD, fracID ); + tiff->DeleteTag ( kTIFF_ExifIFD, fracID ); // ! The subseconds are always in the Exif IFD. return; } + + // Format using all of the numbers. Then overwrite blanks for missing fields. The fields + // missing from the XMP are detected with length checks: YYYY-MM-DDThh:mm:ss + // < 18 - no seconds + // < 15 - no minutes + // < 12 - no hours + // < 9 - no day + // < 6 - no month + // < 1 - no year + + XMP_DateTime xmpBin; + SXMPUtils::ConvertToDate ( xmpStr.c_str(), &xmpBin ); char buffer[24]; - snprintf ( buffer, sizeof(buffer), "%.4d:%.2d:%.2d %.2d:%.2d:%.2d", // AUDIT: Use of sizeof(buffer) is safe. - xmpValue.year, xmpValue.month, xmpValue.day, xmpValue.hour, xmpValue.minute, xmpValue.second ); - + snprintf ( buffer, sizeof(buffer), "%04d:%02d:%02d %02d:%02d:%02d", // AUDIT: Use of sizeof(buffer) is safe. + xmpBin.year, xmpBin.month, xmpBin.day, xmpBin.hour, xmpBin.minute, xmpBin.second ); + + size_t xmpLen = xmpStr.size(); + if ( xmpLen < 18 ) { + buffer[17] = buffer[18] = ' '; + if ( xmpLen < 15 ) { + buffer[14] = buffer[15] = ' '; + if ( xmpLen < 12 ) { + buffer[11] = buffer[12] = ' '; + if ( xmpLen < 9 ) { + buffer[8] = buffer[9] = ' '; + if ( xmpLen < 6 ) { + buffer[5] = buffer[6] = ' '; + if ( xmpLen < 1 ) { + buffer[0] = buffer[1] = buffer[2] = buffer[3] = ' '; + } + } + } + } + } + } + tiff->SetTag_ASCII ( mainIFD, mainID, buffer ); + + if ( xmpBin.nanoSecond == 0 ) { + + tiff->DeleteTag ( kTIFF_ExifIFD, fracID ); - if ( xmpValue.nanoSecond != 0 ) { + } else { - snprintf ( buffer, sizeof(buffer), "%09d", xmpValue.nanoSecond ); // AUDIT: Use of sizeof(buffer) is safe. + snprintf ( buffer, sizeof(buffer), "%09d", xmpBin.nanoSecond ); // AUDIT: Use of sizeof(buffer) is safe. for ( size_t i = strlen(buffer)-1; i > 0; --i ) { if ( buffer[i] != '0' ) break; buffer[i] = 0; // Strip trailing zero digits. } - tiff->SetTag_ASCII ( fracIFD, fracID, buffer ); + tiff->SetTag_ASCII ( kTIFF_ExifIFD, fracID, buffer ); // ! The subseconds are always in the Exif IFD. } @@ -2267,10 +2488,55 @@ ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp // Do nothing, let other exports proceed. // ? Notify client? } - + } // ExportTIFF_Date // ================================================================================================= +// ExportTIFF_ArrayASCII +// ===================== +// +// Catenate all of the XMP array values into a string. Use a "; " separator for Artist, nul for others. + +static void +ExportTIFF_ArrayASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, + TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +{ + try { // Don't let errors with one stop the others. + + std::string itemValue, fullValue; + XMP_OptionBits xmpFlags; + + bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + if ( ! foundXMP ) { + tiff->DeleteTag ( ifd, id ); + return; + } + + if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the tag? + + if ( id == kTIFF_Artist ) { + SXMPUtils::CatenateArrayItems ( xmp, xmpNS, xmpProp, 0, 0, kXMP_PropArrayIsOrdered, &fullValue ); + fullValue += '\x0'; // ! Need explicit final nul for SetTag below. + } else { + size_t count = xmp.CountArrayItems ( xmpNS, xmpProp ); + for ( size_t i = 1; i <= count; ++i ) { // ! XMP arrays are indexed from 1. + (void) xmp.GetArrayItem ( xmpNS, xmpProp, (XMP_Index)i, &itemValue, &xmpFlags ); + if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? + fullValue.append ( itemValue ); + fullValue.append ( 1, '\x0' ); + } + } + + tiff->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)fullValue.size(), fullValue.c_str() ); // ! Already have trailing nul. + + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } + +} // ExportTIFF_ArrayASCII + +// ================================================================================================= // ExportTIFF_LocTextASCII // ====================== @@ -2287,14 +2553,14 @@ ExportTIFF_LocTextASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * tiff->DeleteTag ( ifd, id ); return; } - + tiff->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)( xmpValue.size()+1 ), xmpValue.c_str() ); } catch ( ... ) { // Do nothing, let other exports proceed. // ? Notify client? } - + } // ExportTIFF_LocTextASCII // ================================================================================================= @@ -2323,22 +2589,22 @@ ExportTIFF_EncodedString ( const SXMPMeta & xmp, const char * xmpNS, const char bool ok = xmp.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &xmpValue, 0 ); if ( ! ok ) return; // ? Complain? Delete the tag? } - + XMP_Uns8 encoding = kTIFF_EncodeASCII; for ( size_t i = 0; i < xmpValue.size(); ++i ) { - if ( xmpValue[i] >= 0x80 ) { + if ( (XMP_Uns8)xmpValue[i] >= 0x80 ) { encoding = kTIFF_EncodeUnicode; break; } } - + tiff->SetTag_EncodedString ( ifd, id, xmpValue.c_str(), encoding ); } catch ( ... ) { // Do nothing, let other exports proceed. // ? Notify client? } - + } // ExportTIFF_EncodedString // ================================================================================================= @@ -2358,9 +2624,9 @@ ExportTIFF_GPSCoordinate ( const SXMPMeta & xmp, const char * xmpNS, const char { XMP_Uns16 refID = _id-1; // ! The GPS refs and locations are all tag N-1 and N pairs. XMP_Uns16 locID = _id; - + XMP_Assert ( (locID & 1) == 0 ); - + try { // Don't let errors with one stop the others. std::string xmpValue; @@ -2372,20 +2638,20 @@ ExportTIFF_GPSCoordinate ( const SXMPMeta & xmp, const char * xmpNS, const char tiff->DeleteTag ( ifd, locID ); return; } - + if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; - + const char * chPtr = xmpValue.c_str(); - + XMP_Uns32 deg=0, minNum=0, minDenom=1, sec=0; - + for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) deg = deg*10 + (*chPtr - '0'); if ( *chPtr != ',' ) return; // Bad XMP string. ++chPtr; // Skip the comma. - + for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) minNum = minNum*10 + (*chPtr - '0'); if ( (*chPtr != ',') && (*chPtr != '.') ) return; // Bad XMP string. - + if ( *chPtr == ',' ) { ++chPtr; // Skip the comma. @@ -2402,15 +2668,15 @@ ExportTIFF_GPSCoordinate ( const SXMPMeta & xmp, const char * xmpNS, const char } } - + if ( *(chPtr+1) != 0 ) return; // Bad XMP string. - + char ref[2]; ref[0] = *chPtr; ref[1] = 0; - + tiff->SetTag ( ifd, refID, kTIFF_ASCIIType, 2, &ref[0] ); - + XMP_Uns32 loc[6]; tiff->PutUns32 ( deg, &loc[0] ); tiff->PutUns32 ( 1, &loc[1] ); @@ -2418,120 +2684,251 @@ ExportTIFF_GPSCoordinate ( const SXMPMeta & xmp, const char * xmpNS, const char tiff->PutUns32 ( minDenom, &loc[3] ); tiff->PutUns32 ( sec, &loc[4] ); tiff->PutUns32 ( 1, &loc[5] ); - + tiff->SetTag ( ifd, locID, kTIFF_RationalType, 3, &loc[0] ); } catch ( ... ) { // Do nothing, let other exports proceed. // ? Notify client? } - -} // ExportTIFF_GPSCoordinate -// ================================================================================================= -// ================================================================================================= +} // ExportTIFF_GPSCoordinate // ================================================================================================= -// ReconcileUtils::ExportTIFF -// ========================== +// ExportTIFF_GPSTimeStamp +// ======================= // -// Only a few tags are written back from XMP to the primary IFD, they are each handled explicitly. -// The writeback tags are: -// 270 - ImageDescription -// 274 - Orientation -// 282 - XResolution -// 283 - YResolution -// 296 - ResolutionUnit -// 305 - Software -// 306 - DateTime -// 315 - Artist -// 33432 - Copyright - -// *** need to determine if the XMP has changed - only export when necessary +// The Exif is in 2 tags, GPSTimeStamp and GPSDateStamp. The time is 3 rationals for the hour, minute, +// and second in UTC. The date is a nul terminated string "YYYY:MM:DD". -void -ReconcileUtils::ExportTIFF ( const SXMPMeta & xmp, TIFF_Manager * tiff ) +static const double kBillion = 1000.0*1000.0*1000.0; +static const double mMaxSec = 4.0*kBillion - 1.0; + +static void +ExportTIFF_GPSTimeStamp ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, TIFF_Manager * tiff ) { - ExportTIFF_LocTextASCII ( xmp, kXMP_NS_DC, "description", - tiff, kTIFF_PrimaryIFD, kTIFF_ImageDescription ); - - ExportSingleTIFF_Short ( xmp, kXMP_NS_TIFF, "Orientation", - tiff, kTIFF_PrimaryIFD, kTIFF_Orientation ); - - ExportSingleTIFF_Rational ( xmp, kXMP_NS_TIFF, "XResolution", - tiff, kTIFF_PrimaryIFD, kTIFF_XResolution ); + try { // Don't let errors with one stop the others. - ExportSingleTIFF_Rational ( xmp, kXMP_NS_TIFF, "YResolution", - tiff, kTIFF_PrimaryIFD, kTIFF_YResolution ); + XMP_DateTime binXMP; + bool foundXMP = xmp.GetProperty_Date ( xmpNS, xmpProp, &binXMP, 0 ); + if ( ! foundXMP ) { + tiff->DeleteTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp ); + tiff->DeleteTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDateStamp ); + return; + } + + SXMPUtils::ConvertToUTCTime ( &binXMP ); - ExportSingleTIFF_Short ( xmp, kXMP_NS_TIFF, "ResolutionUnit", - tiff, kTIFF_PrimaryIFD, kTIFF_ResolutionUnit ); + XMP_Uns32 exifTime[6]; + tiff->PutUns32 ( binXMP.hour, &exifTime[0] ); + tiff->PutUns32 ( 1, &exifTime[1] ); + tiff->PutUns32 ( binXMP.minute, &exifTime[2] ); + tiff->PutUns32 ( 1, &exifTime[3] ); + if ( binXMP.nanoSecond == 0 ) { + tiff->PutUns32 ( binXMP.second, &exifTime[4] ); + tiff->PutUns32 ( 1, &exifTime[5] ); + } else { + double fSec = (double)binXMP.second + ((double)binXMP.nanoSecond / kBillion ); + XMP_Uns32 denom = 1000*1000; // Choose microsecond resolution by default. + TIFF_Manager::TagInfo oldInfo; + bool hadExif = tiff->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, &oldInfo ); + if ( hadExif && (oldInfo.type == kTIFF_RationalType) && (oldInfo.count == 3) ) { + XMP_Uns32 oldDenom = tiff->GetUns32 ( &(((XMP_Uns32*)oldInfo.dataPtr)[5]) ); + if ( oldDenom != 1 ) denom = oldDenom; + } + fSec *= denom; + while ( fSec > mMaxSec ) { fSec /= 10; denom /= 10; } + tiff->PutUns32 ( (XMP_Uns32)fSec, &exifTime[4] ); + tiff->PutUns32 ( denom, &exifTime[5] ); + } + tiff->SetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, kTIFF_RationalType, 3, &exifTime[0] ); - ExportSingleTIFF_ASCII ( xmp, kXMP_NS_XMP, "CreatorTool", - tiff, kTIFF_PrimaryIFD, kTIFF_Software ); + char exifDate[16]; // AUDIT: Long enough, only need 11. + snprintf ( exifDate, 12, "%04d:%02d:%02d", binXMP.year, binXMP.month, binXMP.day ); + if ( exifDate[10] == 0 ) { // Make sure there is no value overflow. + tiff->SetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDateStamp, kTIFF_ASCIIType, 11, exifDate ); + } - ExportTIFF_Date ( xmp, kXMP_NS_XMP, "ModifyDate", - tiff, kTIFF_PrimaryIFD, kTIFF_DateTime, kTIFF_ExifIFD, kTIFF_SubSecTime ); - - ExportArrayTIFF_ASCII ( xmp, kXMP_NS_DC, "creator", - tiff, kTIFF_PrimaryIFD, kTIFF_Artist ); + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } - ExportTIFF_LocTextASCII ( xmp, kXMP_NS_DC, "rights", - tiff, kTIFF_PrimaryIFD, kTIFF_Copyright ); +} // ExportTIFF_GPSTimeStamp -} // ReconcileUtils::ExportTIFF; +// ================================================================================================= +// ================================================================================================= // ================================================================================================= -// ReconcileUtils::ExportExif +// PhotoDataUtils::ExportExif // ========================== -// -// Only a few tags are written back from XMP to the Exif and GPS IFDs, they are each handled -// explicitly. The Exif writeback tags are: -// 36867 - DateTimeOriginal (plus 37521 SubSecTimeOriginal) -// 36868 - DateTimeDigitized (plus 37522 SubSecTimeDigitized) -// 37510 - UserComment -// 40964 - RelatedSoundFile -// The GPS writeback tags are: -// 1 - GPSLatitudeRef -// 2 - GPSLatitude -// 3 - GPSLongitudeRef -// 4 - GPSLongitude - -// ! Older versions of Photoshop did not import the UserComment or RelatedSoundFile tags. Don't -// ! export the current XMP unless the original XMP had the tag or the current XMP has the tag. -// ! That is, don't delete the Exif tag if the XMP never had the property. void -ReconcileUtils::ExportExif ( const SXMPMeta & xmp, TIFF_Manager * tiff ) +PhotoDataUtils::ExportExif ( SXMPMeta * xmp, TIFF_Manager * exif ) { + bool haveXMP, haveExif; + std::string xmpValue; + XMP_Int32 int32; + XMP_Uns8 uns8; + + // Do all of the table driven standard exports. + + ExportTIFF_StandardMappings ( kTIFF_PrimaryIFD, exif, *xmp ); + ExportTIFF_StandardMappings ( kTIFF_ExifIFD, exif, *xmp ); + ExportTIFF_StandardMappings ( kTIFF_GPSInfoIFD, exif, *xmp ); + + // Export dc:description to TIFF ImageDescription, and exif:UserComment to EXIF UserComment. + + // *** This is not following the MWG guidelines. The policy here tries to be more backward compatible. + + ExportTIFF_LocTextASCII ( *xmp, kXMP_NS_DC, "description", + exif, kTIFF_PrimaryIFD, kTIFF_ImageDescription ); + + ExportTIFF_EncodedString ( *xmp, kXMP_NS_EXIF, "UserComment", + exif, kTIFF_ExifIFD, kTIFF_UserComment, true /* isLangAlt */ ); + + // Export all of the date/time tags. + // ! Special case: Don't create Exif DateTimeDigitized. This can avoid PSD full rewrite due to + // ! new mapping from xmp:CreateDate. - if ( xmp.DoesPropertyExist ( kXMP_NS_EXIF, "DateTimeOriginal" ) ) { - ExportTIFF_Date ( xmp, kXMP_NS_EXIF, "DateTimeOriginal", - tiff, kTIFF_ExifIFD, kTIFF_DateTimeOriginal, kTIFF_ExifIFD, kTIFF_SubSecTimeOriginal ); + if ( exif->GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeDigitized, 0 ) ) { + ExportTIFF_Date ( *xmp, kXMP_NS_XMP, "CreateDate", exif, kTIFF_DateTimeDigitized ); } - if ( xmp.DoesPropertyExist ( kXMP_NS_EXIF, "DateTimeDigitized" ) ) { - ExportTIFF_Date ( xmp, kXMP_NS_EXIF, "DateTimeDigitized", - tiff, kTIFF_ExifIFD, kTIFF_DateTimeDigitized, kTIFF_ExifIFD, kTIFF_SubSecTimeDigitized ); + ExportTIFF_Date ( *xmp, kXMP_NS_EXIF, "DateTimeOriginal", exif, kTIFF_DateTimeOriginal ); + ExportTIFF_Date ( *xmp, kXMP_NS_XMP, "ModifyDate", exif, kTIFF_DateTime ); + + // 34855 ISOSpeedRatings has special cases for ISO over 65535. The tag is SHORT, some cameras + // omit the tag and some write 65535, all put the real ISO in MakerNote - which ACR might + // extract and leave in the XMP. There are 2 export cases: + // 1. No XMP property, or one or more of the XMP values are over 65535: + // Leave both the XMP and native tag alone. + // 1. Have XMP property and all of the XMP values are under 65535: + // Leave existing native tag, else export; strip the XMP either way. + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + if ( haveXMP ) { + + XMP_Index i, count; + std::string isoValue; + bool haveHighISO = false; + + for ( i = 1, count = xmp->CountArrayItems ( kXMP_NS_EXIF, "ISOSpeedRatings" ); i <= count; ++i ) { + xmp->GetArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", i, &isoValue, 0 ); + if ( SXMPUtils::ConvertToInt ( isoValue.c_str() ) > 0xFFFF ) { haveHighISO = true; break; } + } + + if ( ! haveHighISO ) { + haveExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeedRatings, 0 ); + if ( ! haveExif ) { // ISOSpeedRatings has an inject-only mapping. + ExportArrayTIFF ( exif, kTIFF_ExifIFD, kISOSpeedMapping, exif->IsNativeEndian(), *xmp, kXMP_NS_EXIF, "ISOSpeedRatings" ); + } + xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings"); + } + } + + // Export the remaining TIFF, Exif, and GPS IFD tags. + + ExportTIFF_ArrayASCII ( *xmp, kXMP_NS_DC, "creator", exif, kTIFF_PrimaryIFD, kTIFF_Artist ); - if ( tiff->xmpHadUserComment || xmp.DoesPropertyExist ( kXMP_NS_EXIF, "UserComment" ) ) { - ExportTIFF_EncodedString ( xmp, kXMP_NS_EXIF, "UserComment", - tiff, kTIFF_ExifIFD, kTIFF_UserComment, true /* isLangAlt */ ); + ExportTIFF_LocTextASCII ( *xmp, kXMP_NS_DC, "rights", exif, kTIFF_PrimaryIFD, kTIFF_Copyright ); + + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "ExifVersion", &xmpValue, 0 ); + if ( haveXMP && (xmpValue.size() == 4) && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, 0 )) ) { + // 36864 ExifVersion is 4 "undefined" ASCII characters. + exif->SetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, kTIFF_UndefinedType, 4, xmpValue.data() ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "ComponentsConfiguration" ); + if ( haveXMP && (xmp->CountArrayItems ( kXMP_NS_EXIF, "ComponentsConfiguration" ) == 4) && + (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, 0 )) ) { + // 37121 ComponentsConfiguration is an array of 4 "undefined" UInt8 bytes. + XMP_Uns8 compConfig[4]; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[1]", &int32, 0 ); + compConfig[0] = (XMP_Uns8)int32; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[2]", &int32, 0 ); + compConfig[1] = (XMP_Uns8)int32; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[3]", &int32, 0 ); + compConfig[2] = (XMP_Uns8)int32; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[4]", &int32, 0 ); + compConfig[3] = (XMP_Uns8)int32; + exif->SetTag ( kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, kTIFF_UndefinedType, 4, &compConfig[0] ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "Flash" ); + if ( haveXMP && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_Flash, 0 )) ) { + // 37385 Flash is a UInt16 collection of bit fields and is mapped to a struct in XMP. + XMP_Uns16 binFlash = 0; + bool field; + haveXMP = xmp->GetProperty_Bool ( kXMP_NS_EXIF, "Flash/exif:Fired", &field, 0 ); + if ( haveXMP & field ) binFlash |= 0x0001; + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "Flash/exif:Return", &int32, 0 ); + if ( haveXMP ) binFlash |= (int32 & 3) << 1; + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "Flash/exif:Mode", &int32, 0 ); + if ( haveXMP ) binFlash |= (int32 & 3) << 3; + haveXMP = xmp->GetProperty_Bool ( kXMP_NS_EXIF, "Flash/exif:Function", &field, 0 ); + if ( haveXMP & field ) binFlash |= 0x0020; + haveXMP = xmp->GetProperty_Bool ( kXMP_NS_EXIF, "Flash/exif:RedEyeMode", &field, 0 ); + if ( haveXMP & field ) binFlash |= 0x0040; + exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_Flash, binFlash ); + } + + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "FileSource", &int32, 0 ); + if ( haveXMP && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_FileSource, 0 )) ) { + // 41728 FileSource is an "undefined" UInt8. + uns8 = (XMP_Uns8)int32; + exif->SetTag ( kTIFF_ExifIFD, kTIFF_FileSource, kTIFF_UndefinedType, 1, &uns8 ); } + + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "SceneType", &int32, 0 ); + if ( haveXMP && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_SceneType, 0 )) ) { + // 41729 SceneType is an "undefined" UInt8. + uns8 = (XMP_Uns8)int32; + exif->SetTag ( kTIFF_ExifIFD, kTIFF_SceneType, kTIFF_UndefinedType, 1, &uns8 ); + } + + // *** Deferred inject-only tags: SpatialFrequencyResponse, DeviceSettingDescription, CFAPattern - if ( tiff->xmpHadRelatedSoundFile || xmp.DoesPropertyExist ( kXMP_NS_EXIF, "RelatedSoundFile" ) ) { - ExportSingleTIFF_ASCII ( xmp, kXMP_NS_EXIF, "RelatedSoundFile", - tiff, kTIFF_ExifIFD, kTIFF_RelatedSoundFile ); + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "GPSVersionID", &xmpValue, 0 ); // This is inject-only. + if ( haveXMP && (xmpValue.size() == 7) && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, 0 )) ) { + char gpsID[4]; // 0 GPSVersionID is 4 UInt8 bytes and mapped in XMP as "n.n.n.n". + gpsID[0] = xmpValue[0]; + gpsID[1] = xmpValue[2]; + gpsID[2] = xmpValue[4]; + gpsID[3] = xmpValue[6]; + exif->SetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, kTIFF_ByteType, 4, &gpsID[0] ); } + + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSLatitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSLatitude ); + + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSLongitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSLongitude ); + + ExportTIFF_GPSTimeStamp ( *xmp, kXMP_NS_EXIF, "GPSTimeStamp", exif ); - if ( xmp.DoesPropertyExist ( kXMP_NS_EXIF, "GPSLatitude" ) ) { - ExportTIFF_GPSCoordinate ( xmp, kXMP_NS_EXIF, "GPSLatitude", tiff, kTIFF_GPSInfoIFD, kTIFF_GPSLatitude ); + // The following GPS tags are inject-only. + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "GPSDestLatitude" ); + if ( haveXMP && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude, 0 )) ) { + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSDestLatitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "GPSDestLongitude" ); + if ( haveXMP && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude, 0 )) ) { + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSDestLongitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude ); } - if ( xmp.DoesPropertyExist ( kXMP_NS_EXIF, "GPSLongitude" ) ) { - ExportTIFF_GPSCoordinate ( xmp, kXMP_NS_EXIF, "GPSLongitude", tiff, kTIFF_GPSInfoIFD, kTIFF_GPSLongitude ); + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "GPSProcessingMethod", &xmpValue, 0 ); + if ( haveXMP && (! xmpValue.empty()) && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod, 0 )) ) { + // 27 GPSProcessingMethod is a string with explicit encoding. + ExportTIFF_EncodedString ( *xmp, kXMP_NS_EXIF, "GPSProcessingMethod", exif, kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod ); } -} // ReconcileUtils::ExportExif; + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "GPSAreaInformation", &xmpValue, 0 ); + if ( haveXMP && (! xmpValue.empty()) && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation, 0 )) ) { + // 28 GPSAreaInformation is a string with explicit encoding. + ExportTIFF_EncodedString ( *xmp, kXMP_NS_EXIF, "GPSAreaInformation", exif, kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation ); + } + +} // PhotoDataUtils::ExportExif diff --git a/source/XMPFiles/FormatSupport/Reconcile_Impl.cpp b/source/XMPFiles/FormatSupport/Reconcile_Impl.cpp index 1f06083..7d27769 100644 --- a/source/XMPFiles/FormatSupport/Reconcile_Impl.cpp +++ b/source/XMPFiles/FormatSupport/Reconcile_Impl.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -20,26 +20,26 @@ // ================================================================================================= /// \file Reconcile_Impl.cpp -/// \brief Implementation utilities for the legacy metadata reconciliation support. +/// \brief Implementation utilities for the photo metadata reconciliation support. /// // ================================================================================================= // ================================================================================================= -// IsASCII -// ======= +// ReconcileUtils::IsASCII +// ======================= // // See if a string is 7 bit ASCII. -static inline bool IsASCII ( const void * strPtr, size_t strLen ) +bool ReconcileUtils::IsASCII ( const void * textPtr, size_t textLen ) { - for ( const XMP_Uns8 * strPos = (XMP_Uns8*)strPtr; strLen > 0; --strLen, ++strPos ) { - if ( *strPos >= 0x80 ) return false; + for ( const XMP_Uns8 * textPos = (XMP_Uns8*)textPtr; textLen > 0; --textLen, ++textPos ) { + if ( *textPos >= 0x80 ) return false; } return true; -} // IsASCII +} // ReconcileUtils::IsASCII // ================================================================================================= // ReconcileUtils::IsUTF8 @@ -49,16 +49,16 @@ static inline bool IsASCII ( const void * strPtr, size_t strLen ) // strings. We don't use CodePoint_from_UTF8_Multi in UnicodeConversions because it throws an // exception for non-Unicode and we don't need to actually compute the code points. -bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) +bool ReconcileUtils::IsUTF8 ( const void * textPtr, size_t textLen ) { - const XMP_Uns8 * utf8Pos = (XMP_Uns8*)utf8Ptr; - const XMP_Uns8 * utf8End = utf8Pos + utf8Len; + const XMP_Uns8 * textPos = (XMP_Uns8*)textPtr; + const XMP_Uns8 * textEnd = textPos + textLen; - while ( utf8Pos < utf8End ) { + while ( textPos < textEnd ) { - if ( *utf8Pos < 0x80 ) { + if ( *textPos < 0x80 ) { - ++utf8Pos; // ASCII is UTF-8, tolerate nuls. + ++textPos; // ASCII is UTF-8, tolerate nuls. } else { @@ -68,26 +68,26 @@ bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) #if 0 // *** This might be a more effcient way to count the bytes. static XMP_Uns8 kByteCounts[16] = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 3, 4 }; - size_t bytesNeeded = kByteCounts [ *utf8Pos >> 4 ]; - if ( (bytesNeeded < 2) || ((bytesNeeded == 4) && ((*utf8Pos & 0x08) != 0)) ) return false; - if ( (utf8Pos + bytesNeeded) > utf8End ) return false; + size_t bytesNeeded = kByteCounts [ *textPos >> 4 ]; + if ( (bytesNeeded < 2) || ((bytesNeeded == 4) && ((*textPos & 0x08) != 0)) ) return false; + if ( (textPos + bytesNeeded) > textEnd ) return false; #endif size_t bytesNeeded = 0; // Count the high order 1 bits in the first byte. - for ( XMP_Uns8 temp = *utf8Pos; temp > 0x7F; temp = temp << 1 ) ++bytesNeeded; + for ( XMP_Uns8 temp = *textPos; temp > 0x7F; temp = temp << 1 ) ++bytesNeeded; // *** Consider CPU-specific assembly inline, e.g. cntlzw on PowerPC. - if ( (bytesNeeded < 2) || (bytesNeeded > 4) || ((utf8Pos+bytesNeeded) > utf8End) ) return false; + if ( (bytesNeeded < 2) || (bytesNeeded > 4) || ((textPos+bytesNeeded) > textEnd) ) return false; - for ( --bytesNeeded, ++utf8Pos; bytesNeeded > 0; --bytesNeeded, ++utf8Pos ) { - if ( (*utf8Pos >> 6) != 2 ) return false; + for ( --bytesNeeded, ++textPos; bytesNeeded > 0; --bytesNeeded, ++textPos ) { + if ( (*textPos >> 6) != 2 ) return false; } } } - return true; + return true; // ! Returns true for empty strings. } // ReconcileUtils::IsUTF8 @@ -97,8 +97,7 @@ bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) #if XMP_WinBuild - static void UTF8ToWinEncoding ( UINT codePage, - const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ) + void ReconcileUtils::UTF8ToWinEncoding ( UINT codePage, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ) { std::string utf16; // WideCharToMultiByte wants native UTF-16. @@ -117,11 +116,15 @@ bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) #elif XMP_MacBuild - static void UTF8ToMacEncoding ( TextEncoding & destEncoding, - const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ) + void ReconcileUtils::UTF8ToMacEncoding ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ) { OSStatus err; + TextEncoding destEncoding; + if ( macLang == langUnspecified ) macLang = kTextLanguageDontCare; + err = UpgradeScriptInfoToTextEncoding ( macScript, macLang, kTextRegionDontCare, 0, &destEncoding ); + if ( err != noErr ) XMP_Throw ( "UpgradeScriptInfoToTextEncoding failed", kXMPErr_ExternalFailure ); + UnicodeMapping mappingInfo; mappingInfo.mappingVersion = kUnicodeUseLatestMapping; mappingInfo.otherEncoding = GetTextEncodingBase ( destEncoding ); @@ -167,8 +170,7 @@ bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) #elif XMP_UNIXBuild - // ! Does not exist, must not be called, for Generic UNIX builds. It is not clear at this time - // ! what notion of local encoding should be used for generic UNIX, especially in a server product. + // ! Does not exist, must not be called, for Generic UNIX builds. #endif @@ -176,17 +178,13 @@ bool ReconcileUtils::IsUTF8 ( const void * utf8Ptr, size_t utf8Len ) // ReconcileUtils::UTF8ToLocal // =========================== -#if ! XMP_UNIXBuild -// ! Does not exist, must not be called, for Generic UNIX builds. It is not clear at this time -// ! what notion of local encoding should be used for generic UNIX, especially in a server product. - void ReconcileUtils::UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::string * local ) { const XMP_Uns8* utf8Ptr = (XMP_Uns8*)_utf8Ptr; local->erase(); - if ( IsASCII ( utf8Ptr, utf8Len ) ) { + if ( ReconcileUtils::IsASCII ( utf8Ptr, utf8Len ) ) { local->assign ( (const char *)utf8Ptr, utf8Len ); return; } @@ -197,76 +195,87 @@ void ReconcileUtils::UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::s #elif XMP_MacBuild - OSStatus err; - - TextEncoding localEncoding; - err = UpgradeScriptInfoToTextEncoding ( smSystemScript, - kTextLanguageDontCare, kTextRegionDontCare, 0, &localEncoding ); - if ( err != noErr ) XMP_Throw ( "UpgradeScriptInfoToTextEncoding failed", kXMPErr_ExternalFailure ); - - UTF8ToMacEncoding ( localEncoding, utf8Ptr, utf8Len, local ); + UTF8ToMacEncoding ( smSystemScript, kTextLanguageDontCare, utf8Ptr, utf8Len, local ); #elif XMP_UNIXBuild - #error "No generic UNIX implementation" + XMP_Throw ( "Generic UNIX does not have conversions between local and Unicode", kXMPErr_Unavailable ); #endif } // ReconcileUtils::UTF8ToLocal -#endif - // ================================================================================================= // ReconcileUtils::UTF8ToLatin1 // ============================ -// -// Actually to the Windows code page 1252 superset of 8859-1. - -#if ! XMP_UNIXBuild -// ! Does not exist, must not be called, for Generic UNIX builds. At some point we could consider -// ! creating our own private implementation. So far only needed for the ID3 legacy in MP3 files. void ReconcileUtils::UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std::string * latin1 ) { const XMP_Uns8* utf8Ptr = (XMP_Uns8*)_utf8Ptr; + const XMP_Uns8* utf8End = utf8Ptr + utf8Len; latin1->erase(); + latin1->reserve ( utf8Len ); // As good a guess as any, at least enough, exact for ASCII. - if ( IsASCII ( utf8Ptr, utf8Len ) ) { - latin1->assign ( (const char *)utf8Ptr, utf8Len ); - return; - } - - #if XMP_WinBuild - - UTF8ToWinEncoding ( 1252, utf8Ptr, utf8Len, latin1 ); + bool inBadRun = false; - #elif XMP_MacBuild - - TextEncoding latin1Encoding; - latin1Encoding = CreateTextEncoding ( kTextEncodingWindowsLatin1, - kTextEncodingDefaultVariant, kTextEncodingDefaultFormat ); - - UTF8ToMacEncoding ( latin1Encoding, utf8Ptr, utf8Len, latin1 ); + while ( utf8Ptr < utf8End ) { - #elif XMP_UNIXBuild + if ( *utf8Ptr <= 0x7F ) { + + (*latin1) += (char)*utf8Ptr; // Have an ASCII character. + inBadRun = false; + ++utf8Ptr; + + } else if ( utf8Ptr == (utf8End - 1) ) { + + inBadRun = false; + ++utf8Ptr; // Ignore a bad end to the UTF-8. + + } else { + + XMP_Assert ( (utf8End - utf8Ptr) >= 2 ); + XMP_Uns16 ch16 = GetUns16BE ( utf8Ptr ); // A Latin-1 80..FF is 2 UTF-8 bytes. + + if ( (0xC280 <= ch16) && (ch16 <= 0xC2BF) ) { + + (*latin1) += (char)(ch16 & 0xFF); // UTF-8 C280..C2BF are Latin-1 80..BF. + inBadRun = false; + utf8Ptr += 2; + + } else if ( (0xC380 <= ch16) && (ch16 <= 0xC3BF) ) { + + (*latin1) += (char)((ch16 & 0xFF) + 0x40); // UTF-8 C380..C3BF are Latin-1 C0..FF. + inBadRun = false; + utf8Ptr += 2; + + } else { + + if ( ! inBadRun ) { + inBadRun = true; + (*latin1) += "(?)"; // Mark the run of out of scope UTF-8. + } + + ++utf8Ptr; // Skip the presumably well-formed UTF-8 character. + while ( (utf8Ptr < utf8End) && ((*utf8Ptr & 0xC0) == 0x80) ) ++utf8Ptr; + + } + + } - #error "No generic UNIX implementation" + } - #endif + XMP_Assert ( utf8Ptr == utf8End ); } // ReconcileUtils::UTF8ToLatin1 -#endif - // ================================================================================================= // HostEncodingToUTF8 // ================== #if XMP_WinBuild - static void WinEncodingToUTF8 ( UINT codePage, - const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ) + void ReconcileUtils::WinEncodingToUTF8 ( UINT codePage, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ) { int utf16Len = MultiByteToWideChar ( codePage, 0, (LPCSTR)hostPtr, (int)hostLen, 0, 0 ); @@ -279,11 +288,15 @@ void ReconcileUtils::UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std:: #elif XMP_MacBuild - static void MacEncodingToUTF8 ( TextEncoding & srcEncoding, - const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ) + void ReconcileUtils::MacEncodingToUTF8 ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ) { OSStatus err; + TextEncoding srcEncoding; + if ( macLang == langUnspecified ) macLang = kTextLanguageDontCare; + err = UpgradeScriptInfoToTextEncoding ( macScript, macLang, kTextRegionDontCare, 0, &srcEncoding ); + if ( err != noErr ) XMP_Throw ( "UpgradeScriptInfoToTextEncoding failed", kXMPErr_ExternalFailure ); + UnicodeMapping mappingInfo; mappingInfo.mappingVersion = kUnicodeUseLatestMapping; mappingInfo.otherEncoding = GetTextEncodingBase ( srcEncoding ); @@ -327,8 +340,7 @@ void ReconcileUtils::UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std:: #elif XMP_UNIXBuild - // ! Does not exist, must not be called, for Generic UNIX builds. It is not clear at this time - // ! what notion of local encoding should be used for generic UNIX, especially in a server product. + // ! Does not exist, must not be called, for Generic UNIX builds. #endif @@ -336,17 +348,13 @@ void ReconcileUtils::UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std:: // ReconcileUtils::LocalToUTF8 // =========================== -#if ! XMP_UNIXBuild -// ! Does not exist, must not be called, for Generic UNIX builds. It is not clear at this time -// ! what notion of local encoding should be used for generic UNIX, especially in a server product. - void ReconcileUtils::LocalToUTF8 ( const void * _localPtr, size_t localLen, std::string * utf8 ) { const XMP_Uns8* localPtr = (XMP_Uns8*)_localPtr; utf8->erase(); - if ( IsASCII ( localPtr, localLen ) ) { + if ( ReconcileUtils::IsASCII ( localPtr, localLen ) ) { utf8->assign ( (const char *)localPtr, localLen ); return; } @@ -357,63 +365,42 @@ void ReconcileUtils::LocalToUTF8 ( const void * _localPtr, size_t localLen, std: #elif XMP_MacBuild - OSStatus err; - - TextEncoding localEncoding; - err = UpgradeScriptInfoToTextEncoding ( smSystemScript, kTextLanguageDontCare, kTextRegionDontCare, 0, &localEncoding ); - if ( err != noErr ) XMP_Throw ( "UpgradeScriptInfoToTextEncoding failed", kXMPErr_ExternalFailure ); - - MacEncodingToUTF8 ( localEncoding, localPtr, localLen, utf8 ); + MacEncodingToUTF8 ( smSystemScript, kTextLanguageDontCare, localPtr, localLen, utf8 ); #elif XMP_UNIXBuild - #error "No generic UNIX implementation" + XMP_Throw ( "Generic UNIX does not have conversions between local and Unicode", kXMPErr_Unavailable ); #endif } // ReconcileUtils::LocalToUTF8 -#endif - // ================================================================================================= // ReconcileUtils::Latin1ToUTF8 // ============================ -// -// Actually from the Windows code page 1252 superset of 8859-1. - -#if ! XMP_UNIXBuild -// ! Does not exist, must not be called, for Generic UNIX builds. At some point we could consider -// ! creating our own private implementation. So far only needed for the ID3 legacy in MP3 files. void ReconcileUtils::Latin1ToUTF8 ( const void * _latin1Ptr, size_t latin1Len, std::string * utf8 ) { const XMP_Uns8* latin1Ptr = (XMP_Uns8*)_latin1Ptr; + const XMP_Uns8* latin1End = latin1Ptr + latin1Len; utf8->erase(); + utf8->reserve ( latin1Len ); // As good a guess as any, exact for ASCII. - if ( IsASCII ( latin1Ptr, latin1Len ) ) { - utf8->assign ( (const char *)latin1Ptr, latin1Len ); - return; - } - - #if XMP_WinBuild + for ( ; latin1Ptr < latin1End; ++latin1Ptr ) { - WinEncodingToUTF8 ( 1252, latin1Ptr, latin1Len, utf8 ); + XMP_Uns8 ch8 = *latin1Ptr; - #elif XMP_MacBuild - - TextEncoding latin1Encoding; - latin1Encoding = CreateTextEncoding ( kTextEncodingWindowsLatin1, - kTextEncodingDefaultVariant, kTextEncodingDefaultFormat ); - - MacEncodingToUTF8 ( latin1Encoding, latin1Ptr, latin1Len, utf8 ); - - #elif XMP_UNIXBuild + if ( ch8 <= 0x7F ) { + (*utf8) += (char)ch8; // Have an ASCII character. + } else if ( ch8 <= 0xBF ) { + (*utf8) += 0xC2; // Latin-1 80..BF are UTF-8 C280..C2BF. + (*utf8) += (char)ch8; + } else { + (*utf8) += 0xC3; // Latin-1 C0..FF are UTF-8 C380..C3BF. + (*utf8) += (char)(ch8 - 0x40); + } - #error "No generic UNIX implementation" + } - #endif - } // ReconcileUtils::Latin1ToUTF8 - -#endif diff --git a/source/XMPFiles/FormatSupport/Reconcile_Impl.hpp b/source/XMPFiles/FormatSupport/Reconcile_Impl.hpp index 5fe59a7..19ea865 100644 --- a/source/XMPFiles/FormatSupport/Reconcile_Impl.hpp +++ b/source/XMPFiles/FormatSupport/Reconcile_Impl.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -30,37 +30,66 @@ enum { }; namespace ReconcileUtils { - + + // *** These ought to be with the Unicode conversions. + static const char * kHexDigits = "0123456789ABCDEF"; + + bool IsASCII ( const void * _textPtr, size_t textLen ); + bool IsUTF8 ( const void * _textPtr, size_t textLen ); + + void UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::string * local ); + void UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std::string * latin1 ); + void LocalToUTF8 ( const void * _localPtr, size_t localLen, std::string * utf8 ); + void Latin1ToUTF8 ( const void * _latin1Ptr, size_t latin1Len, std::string * utf8 ); - bool IsUTF8 ( const void * _utf8Ptr, size_t utf8Len ); - - #if ! XMP_UNIXBuild // Remove from generic UNIX until legacy-as-local issues are resolved. - void UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::string * local ); - void UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std::string * latin1 ); - void LocalToUTF8 ( const void * _localPtr, size_t localLen, std::string * utf8 ); - void Latin1ToUTF8 ( const void * _latin1Ptr, size_t latin1Len, std::string * utf8 ); - // *** These ought to be with the Unicode conversions. + #if XMP_WinBuild + void UTF8ToWinEncoding ( UINT codePage, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ); + void WinEncodingToUTF8 ( UINT codePage, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ); + #elif XMP_MacBuild + void UTF8ToMacEncoding ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ); + void MacEncodingToUTF8 ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ); #endif - int CheckIPTCDigest ( IPTC_Manager * iptc, const PSIR_Manager & psir ); - int CheckTIFFDigest ( const TIFF_Manager & tiff, const SXMPMeta & xmp ); - int CheckExifDigest ( const TIFF_Manager & tiff, const SXMPMeta & xmp ); +}; // ReconcileUtils - void SetIPTCDigest ( IPTC_Manager * iptc, PSIR_Manager * psir ); - void SetTIFFDigest ( const TIFF_Manager & tiff, SXMPMeta * xmp ); - void SetExifDigest ( const TIFF_Manager & tiff, SXMPMeta * xmp ); - - void ImportIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int digestState ); - void ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int digestState ); - void ImportTIFF ( const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState, XMP_FileFormat srcFormat ); - void ImportExif ( const TIFF_Manager & tiff, SXMPMeta * xmp, int digestState ); +namespace PhotoDataUtils { + + int CheckIPTCDigest ( const void * newPtr, const XMP_Uns32 newLen, const void * oldDigest ); + void SetIPTCDigest ( void * iptcPtr, XMP_Uns32 iptcLen, PSIR_Manager * psir ); + + bool GetNativeInfo ( const TIFF_Manager & exif, XMP_Uns8 ifd, XMP_Uns16 id, TIFF_Manager::TagInfo * info ); + size_t GetNativeInfo ( const IPTC_Manager & iptc, XMP_Uns8 id, int digestState, + bool haveXMP, IPTC_Manager::DataSetInfo * info ); - void ExportIPTC ( SXMPMeta * xmp, IPTC_Manager * iptc ); // ! Has XMP side effects! + bool IsValueDifferent ( const TIFF_Manager::TagInfo & exifInfo, + const std::string & xmpValue, std::string * exifValue ); + bool IsValueDifferent ( const IPTC_Manager & newIPTC, const IPTC_Manager & oldIPTC, XMP_Uns8 id ); + + void ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int iptcDigestState ); + + void Import2WayIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ); + void Import2WayExif ( const TIFF_Manager & exif, SXMPMeta * xmp, int iptcDigestState ); + + void Import3WayItems ( const TIFF_Manager & exif, const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ); + void ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ); - void ExportTIFF ( const SXMPMeta & xmp, TIFF_Manager * tiff ); - void ExportExif ( const SXMPMeta & xmp, TIFF_Manager * tiff ); + void ExportIPTC ( const SXMPMeta & xmp, IPTC_Manager * iptc ); + void ExportExif ( SXMPMeta * xmp, TIFF_Manager * exif ); + + // These need to be exposed for use in Import3WayItem: -}; // ReconcileUtils + void ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ); + + void ImportIPTC_LangAlt ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ); + + void ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ); + + void ImportIPTC_Date ( XMP_Uns8 dateID, const IPTC_Manager & iptc, SXMPMeta * xmp ); + +}; // PhotoDataUtils #endif // __Reconcile_Impl_hpp__ diff --git a/source/XMPFiles/FormatSupport/SWF_Support.cpp b/source/XMPFiles/FormatSupport/SWF_Support.cpp index 0a22cf0..d9b8115 100644 --- a/source/XMPFiles/FormatSupport/SWF_Support.cpp +++ b/source/XMPFiles/FormatSupport/SWF_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2007 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -16,7 +16,7 @@ namespace SWF_Support // ============================================================================================= - int CalcHeaderSize ( IO::InputStream* inputStream ) + static int CalcHeaderSize ( IO::InputStream* inputStream ) { int size = 0; @@ -44,7 +44,7 @@ namespace SWF_Support // ============================================================================================= - unsigned long CheckTag ( IO::InputStream* inputStream, TagState& inOutTagState, TagData& inOutTagData ) + static unsigned long CheckTag ( IO::InputStream* inputStream, TagState& inOutTagState, TagData& inOutTagData ) { unsigned long ret = 0; XMP_Uns8 * buffer = 0; diff --git a/source/XMPFiles/FormatSupport/SWF_Support.hpp b/source/XMPFiles/FormatSupport/SWF_Support.hpp index ca301b9..db20ab2 100644 --- a/source/XMPFiles/FormatSupport/SWF_Support.hpp +++ b/source/XMPFiles/FormatSupport/SWF_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp b/source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp index 05372b8..3eb6eb2 100644 --- a/source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp +++ b/source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -33,15 +33,15 @@ TIFF_FileWriter::TIFF_FileWriter() : changed(false), legacyDeleted(false), memPa { XMP_Uns8 bogusTIFF [kEmptyTIFFLength]; - + bogusTIFF[0] = 0x4D; bogusTIFF[1] = 0x4D; bogusTIFF[2] = 0x00; bogusTIFF[3] = 0x2A; bogusTIFF[4] = bogusTIFF[5] = bogusTIFF[6] = bogusTIFF[7] = 0x00; - + (void) this->CheckTIFFHeader ( bogusTIFF, sizeof ( bogusTIFF ) ); - + } // TIFF_FileWriter::TIFF_FileWriter // ================================================================================================= @@ -52,7 +52,6 @@ TIFF_FileWriter::~TIFF_FileWriter() { XMP_Assert ( ! (this->memParsed && this->fileParsed) ); - if ( this->fileParsed && (this->jpegTNailPtr != 0) ) free ( this->jpegTNailPtr ); if ( this->ownedStream ) { XMP_Assert ( this->memStream != 0 ); free ( this->memStream ); @@ -117,17 +116,17 @@ const TIFF_FileWriter::InternalTagInfo* TIFF_FileWriter::FindTagInIFD ( XMP_Uns8 // TIFF_FileWriter::GetIFD // ======================= -bool TIFF_FileWriter::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const +bool TIFF_FileWriter::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const { if ( ifd > kTIFF_LastRealIFD ) XMP_Throw ( "Invalid IFD number", kXMPErr_BadParam ); const InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap; InternalTagMap::const_iterator tagPos = currIFD.begin(); InternalTagMap::const_iterator tagEnd = currIFD.end(); - + if ( ifdMap != 0 ) ifdMap->clear(); if ( tagPos == tagEnd ) return false; // Empty IFD. - + if ( ifdMap != 0 ) { for ( ; tagPos != tagEnd; ++tagPos ) { const InternalTagInfo& intInfo = tagPos->second; @@ -135,7 +134,7 @@ bool TIFF_FileWriter::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const (*ifdMap)[intInfo.id] = extInfo; } } - + return true; } // TIFF_FileWriter::GetIFD @@ -148,20 +147,20 @@ XMP_Uns32 TIFF_FileWriter::GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const { const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( (thisTag == 0) || (thisTag->origDataLen == 0) ) return 0; - + return thisTag->origDataOffset; - + } // TIFF_FileWriter::GetValueOffset // ================================================================================================= // TIFF_FileWriter::GetTag // ======================= -bool TIFF_FileWriter::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const +bool TIFF_FileWriter::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const { const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; - + if ( info != 0 ) { info->id = thisTag->id; @@ -171,21 +170,21 @@ bool TIFF_FileWriter::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const info->dataPtr = (const void*)(thisTag->dataPtr); } - + return true; - + } // TIFF_FileWriter::GetTag // ================================================================================================= // TIFF_FileWriter::SetTag // ======================= -void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* clientPtr ) +void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* clientPtr ) { if ( (type < kTIFF_ByteType) || (type > kTIFF_LastType) ) XMP_Throw ( "Invalid TIFF tag type", kXMPErr_BadParam ); size_t typeSize = kTIFF_TypeSizes[type]; size_t fullSize = count * typeSize; - + ifd = PickIFD ( ifd, id ); InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap; @@ -210,7 +209,7 @@ void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_U } tagPtr->FreeData(); // Release any existing data allocation. - + tagPtr->type = type; // These might be changing also. tagPtr->count = count; @@ -218,7 +217,7 @@ void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_U tagPtr->changed = true; tagPtr->dataLen = (XMP_Uns32)fullSize; - + if ( fullSize <= 4 ) { // The data is less than 4 bytes, store it in the smallValue field using native endianness. tagPtr->dataPtr = (XMP_Uns8*) &tagPtr->smallValue; @@ -228,7 +227,7 @@ void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_U if ( tagPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); } memcpy ( tagPtr->dataPtr, clientPtr, fullSize ); // AUDIT: Safe, space guaranteed to be fullSize. - + if ( ! this->nativeEndian ) { if ( typeSize == 2 ) { XMP_Uns16* flipPtr = (XMP_Uns16*) tagPtr->dataPtr; @@ -241,7 +240,7 @@ void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_U for ( XMP_Uns32 i = 0; i < count; ++i ) Flip8 ( flipPtr[i] ); } } - + this->containedIFDs[ifd].changed = true; this->changed = true; @@ -251,11 +250,11 @@ void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_U // TIFF_FileWriter::DeleteTag // ========================== -void TIFF_FileWriter::DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) +void TIFF_FileWriter::DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) { ifd = PickIFD ( ifd, id ); InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap; - + InternalTagMap::iterator tagPos = currIFD.find ( id ); if ( tagPos == currIFD.end() ) return; // ! Don't set the changed flags if the tag didn't exist. @@ -270,15 +269,15 @@ void TIFF_FileWriter::DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) // TIFF_FileWriter::GetTag_Integer // =============================== -bool TIFF_FileWriter::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const +bool TIFF_FileWriter::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const { const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( thisTag->count != 1 ) return false; - + static XMP_Uns32 voidValue; if ( data == 0 ) data = &voidValue; - + if ( thisTag->type == kTIFF_ShortType ) { *data = this->GetUns16 ( thisTag->dataPtr ); } else if ( thisTag->type == kTIFF_LongType ) { @@ -286,7 +285,7 @@ bool TIFF_FileWriter::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* da } else { return false; } - + return true; } // TIFF_FileWriter::GetTag_Integer @@ -300,7 +299,7 @@ bool TIFF_FileWriter::GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_ByteType) || (thisTag->dataLen != 1) ) return false; - + if ( data != 0 ) *data = *thisTag->dataPtr; return true; @@ -315,7 +314,7 @@ bool TIFF_FileWriter::GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SByteType) || (thisTag->dataLen != 1) ) return false; - + if ( data != 0 ) *data = *thisTag->dataPtr; return true; @@ -330,7 +329,7 @@ bool TIFF_FileWriter::GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_ShortType) || (thisTag->dataLen != 2) ) return false; - + if ( data != 0 ) *data = this->GetUns16 ( thisTag->dataPtr ); return true; @@ -345,7 +344,7 @@ bool TIFF_FileWriter::GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* dat const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SShortType) || (thisTag->dataLen != 2) ) return false; - + if ( data != 0 ) *data = (XMP_Int16) this->GetUns16 ( thisTag->dataPtr ); return true; @@ -360,7 +359,7 @@ bool TIFF_FileWriter::GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_LongType) || (thisTag->dataLen != 4) ) return false; - + if ( data != 0 ) *data = this->GetUns32 ( thisTag->dataPtr ); return true; @@ -375,7 +374,7 @@ bool TIFF_FileWriter::GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SLongType) || (thisTag->dataLen != 4) ) return false; - + if ( data != 0 ) *data = (XMP_Int32) this->GetUns32 ( thisTag->dataPtr ); return true; @@ -390,13 +389,13 @@ bool TIFF_FileWriter::GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* da const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false; if ( (thisTag->type != kTIFF_RationalType) || (thisTag->dataLen != 8) ) return false; - + if ( data != 0 ) { XMP_Uns32* dataPtr = (XMP_Uns32*)thisTag->dataPtr; data->num = this->GetUns32 ( dataPtr ); data->denom = this->GetUns32 ( dataPtr+1 ); } - + return true; } // TIFF_FileWriter::GetTag_Rational @@ -410,13 +409,13 @@ bool TIFF_FileWriter::GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false; if ( (thisTag->type != kTIFF_SRationalType) || (thisTag->dataLen != 8) ) return false; - + if ( data != 0 ) { XMP_Uns32* dataPtr = (XMP_Uns32*)thisTag->dataPtr; data->num = (XMP_Int32) this->GetUns32 ( dataPtr ); data->denom = (XMP_Int32) this->GetUns32 ( dataPtr+1 ); } - + return true; } // TIFF_FileWriter::GetTag_SRational @@ -430,8 +429,8 @@ bool TIFF_FileWriter::GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) c const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_FloatType) || (thisTag->dataLen != 4) ) return false; - - if ( data != 0 ) *data = this->GetFloat ( thisTag->dataPtr ); + + if ( data != 0 ) *data = this->GetFloat ( thisTag->dataPtr ); return true; } // TIFF_FileWriter::GetTag_Float @@ -445,8 +444,8 @@ bool TIFF_FileWriter::GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false; if ( (thisTag->type != kTIFF_DoubleType) || (thisTag->dataLen != 8) ) return false; - - if ( data != 0 ) *data = this->GetDouble ( thisTag->dataPtr ); + + if ( data != 0 ) *data = this->GetDouble ( thisTag->dataPtr ); return true; } // TIFF_FileWriter::GetTag_Double @@ -461,10 +460,10 @@ bool TIFF_FileWriter::GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* if ( thisTag == 0 ) return false; if ( (thisTag->dataLen > 4) && (thisTag->dataPtr == 0) ) return false; if ( thisTag->type != kTIFF_ASCIIType ) return false; - + if ( dataPtr != 0 ) *dataPtr = (XMP_StringPtr)thisTag->dataPtr; if ( dataLen != 0 ) *dataLen = thisTag->dataLen; - + return true; } // TIFF_FileWriter::GetTag_ASCII @@ -478,9 +477,9 @@ bool TIFF_FileWriter::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::st const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( thisTag->type != kTIFF_UndefinedType ) return false; - + if ( utf8Str == 0 ) return true; // Return true if the converted string is not wanted. - + bool ok = this->DecodeString ( thisTag->dataPtr, thisTag->dataLen, utf8Str ); return ok; @@ -492,8 +491,10 @@ bool TIFF_FileWriter::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::st void TIFF_FileWriter::SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ) { + std::string encodedStr; - XMP_Throw ( "Not yet implemented", kXMPErr_Unimplemented ); + this->EncodeString ( utf8Str, encoding, &encodedStr ); + this->SetTag ( ifd, id, kTIFF_UndefinedType, (XMP_Uns32)encodedStr.size(), encodedStr.c_str() ); } // TIFF_FileWriter::SetTag_EncodedString @@ -506,22 +507,22 @@ bool TIFF_FileWriter::IsLegacyChanged() if ( ! this->changed ) return false; if ( this->legacyDeleted ) return true; - + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; if ( ! thisIFD.changed ) continue; - + InternalTagMap::iterator tagPos; InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); - + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { InternalTagInfo & thisTag = tagPos->second; if ( thisTag.changed && (thisTag.id != kTIFF_XMP) ) return true; } } - + return false; // Can get here if the XMP tag is the only one changed. } // TIFF_FileWriter::IsLegacyChanged @@ -530,14 +531,14 @@ bool TIFF_FileWriter::IsLegacyChanged() // TIFF_FileWriter::ParseMemoryStream // ================================== -void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) +void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) { this->DeleteExistingInfo(); this->memParsed = true; if ( length == 0 ) return; // Allocate space for the full in-memory stream and copy it. - + if ( ! copyData ) { XMP_Assert ( ! this->ownedStream ); this->memStream = (XMP_Uns8*) data; @@ -551,11 +552,10 @@ void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bo this->tiffLength = length; // Find and process the primary, Exif, GPS, and Interoperability IFDs. - + XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( this->memStream, length ); - XMP_Uns32 tnailIFDOffset = 0; - - if ( primaryIFDOffset != 0 ) tnailIFDOffset = this->ProcessMemoryIFD ( primaryIFDOffset, kTIFF_PrimaryIFD ); + + if ( primaryIFDOffset != 0 ) (void) this->ProcessMemoryIFD ( primaryIFDOffset, kTIFF_PrimaryIFD ); const InternalTagInfo* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->dataLen == 4) ) { @@ -574,19 +574,7 @@ void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bo XMP_Uns32 interopOffset = this->GetUns32 ( interopIFDTag->dataPtr ); (void) this->ProcessMemoryIFD ( interopOffset, kTIFF_InteropIFD ); } - - // Process the thumbnail IFD. We only do this for Exif-compliant TIFF streams. Extract the - // JPEG thumbnail image pointer (tag 513) for later use by GetTNailInfo. - - if ( (tnailIFDOffset != 0) && (! this->containedIFDs[kTIFF_ExifIFD].tagMap.empty()) ) { - (void) this->ProcessMemoryIFD ( tnailIFDOffset, kTIFF_TNailIFD ); - const InternalTagInfo* jpegInfo = FindTagInIFD ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat ); - if ( jpegInfo != 0 ) { - XMP_Uns32 tnailImageOffset = this->GetUns32 ( jpegInfo->dataPtr ); - this->jpegTNailPtr = (XMP_Uns8*)this->memStream + tnailImageOffset; - } - } - + #if 0 { printf ( "\nExiting TIFF_FileWriter::ParseMemoryStream\n" ); @@ -616,27 +604,27 @@ void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bo XMP_Uns32 TIFF_FileWriter::ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) { InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); - + if ( (ifdOffset < 8) || (ifdOffset > (this->tiffLength - kEmptyIFDLength)) ) { XMP_Throw ( "Bad IFD offset", kXMPErr_BadTIFF ); } - + XMP_Uns8* ifdPtr = this->memStream + ifdOffset; XMP_Uns16 tagCount = this->GetUns16 ( ifdPtr ); RawIFDEntry* ifdEntries = (RawIFDEntry*)(ifdPtr+2); if ( tagCount >= 0x8000 ) XMP_Throw ( "Outrageous IFD count", kXMPErr_BadTIFF ); if ( (ifdOffset + 2 + tagCount*12 + 4) > this->tiffLength ) XMP_Throw ( "Out of bounds IFD", kXMPErr_BadTIFF ); - + ifdInfo.origIFDOffset = ifdOffset; ifdInfo.origCount = tagCount; - + for ( size_t i = 0; i < tagCount; ++i ) { - + RawIFDEntry* rawTag = &ifdEntries[i]; XMP_Uns16 tagType = this->GetUns16 ( &rawTag->type ); if ( (tagType < kTIFF_ByteType) || (tagType > kTIFF_LastType) ) continue; // Bad type, skip this tag. - + XMP_Uns16 tagID = this->GetUns16 ( &rawTag->id ); XMP_Uns32 tagCount = this->GetUns32 ( &rawTag->count ); @@ -654,64 +642,17 @@ XMP_Uns32 TIFF_FileWriter::ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd // printf ( "FW_ProcessMemoryIFD tag %d large value @ %.8X\n", mapTag.id, mapTag.dataPtr ); } mapTag.dataPtr = this->memStream + mapTag.origDataOffset; - + } - + ifdPtr += (2 + tagCount*12); ifdInfo.origNextIFD = this->GetUns32 ( ifdPtr ); - + return ifdInfo.origNextIFD; } // TIFF_FileWriter::ProcessMemoryIFD // ================================================================================================= -// CaptureJPEGTNail -// ================ -// -// Capture the JPEG image stream for an Exif compressed thumbnail. - -static XMP_Uns8* CaptureJPEGTNail ( LFA_FileRef fileRef, IOBuffer* ioBuf, const TIFF_Manager& tiff ) -{ - bool ok; - XMP_Uns8* jpegPtr = 0; - XMP_Uns32 jpegOffset, jpegLen; - - ok = tiff.GetTag_Integer ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat, &jpegOffset ); - if ( ok ) ok = tiff.GetTag_Integer ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormatLength, &jpegLen ); - if ( ! ok ) return 0; - - if ( jpegLen > 1024*1024 ) return 0; // ? XMP_Throw ( "Outrageous JPEG TNail length", kXMPErr_BadTIFF ); - - jpegPtr = (XMP_Uns8*) malloc ( jpegLen ); - if ( jpegPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); - - try { - - if ( jpegLen > kIOBufferSize ) { - // This value is bigger than the I/O buffer, read it directly and restore the file position. - LFA_Seek ( fileRef, jpegOffset, SEEK_SET ); - LFA_Read ( fileRef, jpegPtr, jpegLen, kLFA_RequireAll ); - LFA_Seek ( fileRef, (ioBuf->filePos + ioBuf->len), SEEK_SET ); - } else { - // This value can fit in the I/O buffer, so use that. - MoveToOffset ( fileRef, jpegOffset, ioBuf ); - ok = CheckFileSpace ( fileRef, ioBuf, jpegLen ); - if ( ! ok ) XMP_Throw ( "EOF in data block", kXMPErr_BadTIFF ); - memcpy ( jpegPtr, ioBuf->ptr, jpegLen ); // AUDIT: Safe, malloc'ed jpegLen bytes above. - } - - } catch ( ... ) { - - free ( jpegPtr ); - throw; - - } - - return jpegPtr; - -} // CaptureJPEGTNail - -// ================================================================================================= // TIFF_FileWriter::ParseFileStream // ================================ // @@ -720,7 +661,7 @@ static XMP_Uns8* CaptureJPEGTNail ( LFA_FileRef fileRef, IOBuffer* ioBuf, const // and all of their interesting tag values within the first 64K of the file. Well, at least before // we get around to our edit-by-append approach. -void TIFF_FileWriter::ParseFileStream ( LFA_FileRef fileRef ) +void TIFF_FileWriter::ParseFileStream ( LFA_FileRef fileRef ) { bool ok; IOBuffer ioBuf; @@ -729,17 +670,16 @@ void TIFF_FileWriter::ParseFileStream ( LFA_FileRef fileRef ) this->fileParsed = true; this->tiffLength = (XMP_Uns32) LFA_Measure ( fileRef ); if ( this->tiffLength == 0 ) return; - + // Find and process the primary, Exif, GPS, and Interoperability IFDs. - + ioBuf.filePos = LFA_Seek ( fileRef, 0, SEEK_SET ); ok = CheckFileSpace ( fileRef, &ioBuf, 8 ); if ( ! ok ) XMP_Throw ( "TIFF too small", kXMPErr_BadTIFF ); - + XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( ioBuf.ptr, this->tiffLength ); - XMP_Uns32 tnailIFDOffset = 0; - - if ( primaryIFDOffset != 0 ) tnailIFDOffset = this->ProcessFileIFD ( kTIFF_PrimaryIFD, primaryIFDOffset, fileRef, &ioBuf ); + + if ( primaryIFDOffset != 0 ) (void) this->ProcessFileIFD ( kTIFF_PrimaryIFD, primaryIFDOffset, fileRef, &ioBuf ); const InternalTagInfo* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->count == 1) ) { @@ -758,15 +698,7 @@ void TIFF_FileWriter::ParseFileStream ( LFA_FileRef fileRef ) XMP_Uns32 interopOffset = this->GetUns32 ( interopIFDTag->dataPtr ); (void) this->ProcessFileIFD ( kTIFF_InteropIFD, interopOffset, fileRef, &ioBuf ); } - - // Process the thumbnail IFD. We only do this for Exif-compliant TIFF streams. Do this after - // the others since they are often within the first 64K of the file and the thumbnail is not. - if ( (tnailIFDOffset != 0) && (! this->containedIFDs[kTIFF_ExifIFD].tagMap.empty()) ) { - (void) this->ProcessFileIFD ( kTIFF_TNailIFD, tnailIFDOffset, fileRef, &ioBuf ); - this->jpegTNailPtr = CaptureJPEGTNail ( fileRef, &ioBuf, *this ); - } - #if 0 { printf ( "\nExiting TIFF_FileWriter::ParseFileStream\n" ); @@ -793,22 +725,22 @@ void TIFF_FileWriter::ParseFileStream ( LFA_FileRef fileRef ) // TIFF_FileWriter::ProcessFileIFD // =============================== -XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, LFA_FileRef fileRef, IOBuffer* ioBuf ) +XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, LFA_FileRef fileRef, IOBuffer* ioBuf ) { InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); - + MoveToOffset ( fileRef, ifdOffset, ioBuf ); // Move to the start of the IFD. - + bool ok = CheckFileSpace ( fileRef, ioBuf, 2 ); if ( ! ok ) XMP_Throw ( "IFD count missing", kXMPErr_BadTIFF ); XMP_Uns16 tagCount = this->GetUns16 ( ioBuf->ptr ); if ( tagCount >= 0x8000 ) XMP_Throw ( "Outrageous IFD count", kXMPErr_BadTIFF ); if ( (ifdOffset + 2 + tagCount*12 + 4) > this->tiffLength ) XMP_Throw ( "Out of bounds IFD", kXMPErr_BadTIFF ); - + ifdInfo.origIFDOffset = ifdOffset; ifdInfo.origCount = tagCount; - + // --------------------------------------------------------------------------------------------- // First create all of the IFD map entries, capturing short values, and get the next IFD offset. // We're using a std::map for storage, it automatically eliminates duplicates and provides @@ -816,15 +748,15 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, L // value, following Photoshop's behavior. ioBuf->ptr += 2; // Move to the first IFD entry. - + for ( XMP_Uns16 i = 0; i < tagCount; ++i, ioBuf->ptr += 12 ) { - + if ( ! CheckFileSpace ( fileRef, ioBuf, 12 ) ) XMP_Throw ( "EOF within IFD", kXMPErr_BadTIFF ); - + RawIFDEntry* rawTag = (RawIFDEntry*)ioBuf->ptr; XMP_Uns16 tagType = this->GetUns16 ( &rawTag->type ); if ( (tagType < kTIFF_ByteType) || (tagType > kTIFF_LastType) ) continue; // Bad type, skip this tag. - + XMP_Uns16 tagID = this->GetUns16 ( &rawTag->id ); XMP_Uns32 tagCount = this->GetUns32 ( &rawTag->count ); @@ -841,37 +773,37 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, L } else { mapTag.origDataOffset = this->GetUns32 ( &rawTag->dataOrOffset ); // Extract the data offset. } - + } - + if ( ! CheckFileSpace ( fileRef, ioBuf, 4 ) ) XMP_Throw ( "EOF at next IFD offset", kXMPErr_BadTIFF ); ifdInfo.origNextIFD = this->GetUns32 ( ioBuf->ptr ); - + // --------------------------------------------------------------------------------------------- // Go back over the tag map and extract the data for large recognized tags. This is done in 2 // passes, in order to lessen the typical amount of I/O. On the first pass make sure we have at // least 32K of data following the IFD in the buffer, and extract all of the values in that // portion. This should cover an original file, or the appended values with an appended IFD. - + if ( (ioBuf->limit - ioBuf->ptr) < 32*1024 ) RefillBuffer ( fileRef, ioBuf ); - + InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); - + const XMP_Uns16* knownTagPtr = sKnownTags[ifd]; // Points into the ordered recognized tag list. - + XMP_Uns32 bufBegin = (XMP_Uns32)ioBuf->filePos; // TIFF stream bounds for the current buffer. XMP_Uns32 bufEnd = bufBegin + (XMP_Uns32)ioBuf->len; - + for ( ; tagPos != tagEnd; ++tagPos ) { - + InternalTagInfo* currTag = &tagPos->second; if ( currTag->dataLen <= 4 ) continue; // Short values are already in the smallValue field. while ( *knownTagPtr < currTag->id ) ++knownTagPtr; if ( *knownTagPtr != currTag->id ) continue; // Skip unrecognized tags. if ( currTag->dataLen > 1024*1024 ) XMP_Throw ( "Outrageous data length", kXMPErr_BadTIFF ); - + if ( (bufBegin <= currTag->origDataOffset) && ((currTag->origDataOffset + currTag->dataLen) <= bufEnd) ) { // This value is already fully within the current I/O buffer, copy it. MoveToOffset ( fileRef, currTag->origDataOffset, ioBuf ); @@ -879,18 +811,18 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, L if ( currTag->dataPtr == 0 ) XMP_Throw ( "No data block", kXMPErr_NoMemory ); memcpy ( currTag->dataPtr, ioBuf->ptr, currTag->dataLen ); // AUDIT: Safe, malloc'ed currTag->dataLen bytes above. } - + } - + // --------------------------------------------------------------------------------------------- // Now the second large value pass. This will reposition the I/O buffer as necessary. Hopefully // just once, to pick up the span of data not covered in the first pass. - + tagPos = ifdInfo.tagMap.begin(); // Reset both map/array positions. knownTagPtr = sKnownTags[ifd]; - + for ( ; tagPos != tagEnd; ++tagPos ) { - + InternalTagInfo* currTag = &tagPos->second; if ( (currTag->dataLen <= 4) || (currTag->dataPtr != 0) ) continue; // Done this tag? @@ -913,11 +845,11 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, L if ( ! ok ) XMP_Throw ( "EOF in data block", kXMPErr_BadTIFF ); memcpy ( currTag->dataPtr, ioBuf->ptr, currTag->dataLen ); // AUDIT: Safe, malloc'ed currTag->dataLen bytes above. } - + } - + // Done, return the next IFD offset. - + return ifdInfo.origNextIFD; } // TIFF_FileWriter::ProcessFileIFD @@ -928,13 +860,12 @@ XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, L // // See comments for ProcessPShop6IFD. -void TIFF_FileWriter::IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) +void TIFF_FileWriter::IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) { TIFF_MemoryReader buriedExif; buriedExif.ParseMemoryStream ( buriedPtr, (XMP_Uns32) buriedLen ); - + this->ProcessPShop6IFD ( buriedExif, kTIFF_PrimaryIFD ); - this->ProcessPShop6IFD ( buriedExif, kTIFF_TNailIFD ); this->ProcessPShop6IFD ( buriedExif, kTIFF_ExifIFD ); this->ProcessPShop6IFD ( buriedExif, kTIFF_GPSInfoIFD ); @@ -955,7 +886,7 @@ void* TIFF_FileWriter::CopyTagToMasterIFD ( const TagInfo & ps6Tag, InternalIFDI InternalTagInfo& newTag = newPos->second; newTag.dataLen = ps6Tag.dataLen; - + if ( newTag.dataLen <= 4 ) { newTag.dataPtr = (XMP_Uns8*) &newTag.smallValue; newTag.smallValue = *((XMP_Uns32*)ps6Tag.dataPtr); @@ -967,9 +898,9 @@ void* TIFF_FileWriter::CopyTagToMasterIFD ( const TagInfo & ps6Tag, InternalIFDI newTag.changed = true; // ! See comments with ProcessPShop6IFD. XMP_Assert ( (newTag.origDataLen == 0) && (newTag.origDataOffset == 0) ); - + masterIFD->changed = true; - + return newPos->second.dataPtr; // ! Return the address within the map entry for small values. } // TIFF_FileWriter::CopyTagToMasterIFD @@ -983,17 +914,17 @@ void* TIFF_FileWriter::CopyTagToMasterIFD ( const TagInfo & ps6Tag, InternalIFDI static bool FlipCFATable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 ) { if ( tagLen < 4 ) return false; - + XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr; Flip2 ( &u16Ptr[0] ); // Flip the counts to match the master TIFF. Flip2 ( &u16Ptr[1] ); - + XMP_Uns16 columns = GetUns16 ( &u16Ptr[0] ); // Fetch using the master TIFF's routine. XMP_Uns16 rows = GetUns16 ( &u16Ptr[1] ); - + if ( tagLen != (XMP_Uns32)(4 + columns*rows) ) return false; - + return true; } // FlipCFATable @@ -1010,12 +941,12 @@ static bool FlipCFATable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns static bool FlipDSDTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 ) { if ( tagLen < 4 ) return false; - + XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr; for ( size_t i = tagLen/2; i > 0; --i, ++u16Ptr ) Flip2 ( u16Ptr ); - + return true; - + } // FlipDSDTable // ================================================================================================= @@ -1033,20 +964,20 @@ static bool FlipOECFSFRTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc Ge Flip2 ( &u16Ptr[0] ); // Flip the data to match the master TIFF. Flip2 ( &u16Ptr[1] ); - + XMP_Uns16 columns = GetUns16 ( &u16Ptr[0] ); // Fetch using the master TIFF's routine. XMP_Uns16 rows = GetUns16 ( &u16Ptr[1] ); - + XMP_Uns32 minLen = 4 + columns + (8 * columns * rows); // Minimum legit tag size. if ( tagLen < minLen ) return false; - + // Compute the start of the rationals from the end of value. No need to walk through the names. XMP_Uns32* u32Ptr = (XMP_Uns32*) ((XMP_Uns8*)voidPtr + tagLen - (8 * columns * rows)); for ( size_t i = 2*columns*rows; i > 0; --i, ++u32Ptr ) Flip4 ( u32Ptr ); - + return true; - + } // FlipOECFSFRTable // ================================================================================================= @@ -1058,7 +989,7 @@ static bool FlipOECFSFRTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc Ge // tags up to the parent file. Existing tags are not replaced. // // While it is tempting to try to directly use the TIFF_MemoryReader's tweaked IFD info, making that -// visible would compromise implementation separation. Better to pay the modest runtime cost of +// visible would compromise implementation separation. Better to pay the modest runtime cost of // using the official GetIFD method, letting it build the map. // // The tags that get moved are marked as being changed, as is the IFD they are moved into, but the @@ -1075,41 +1006,41 @@ void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XM { bool ok, found; TagInfoMap ps6IFD; - + found = buriedExif.GetIFD ( ifd, &ps6IFD ); if ( ! found ) return; - + bool needsFlipping = (this->bigEndian != buriedExif.IsBigEndian()); - + InternalIFDInfo* masterIFD = &this->containedIFDs[ifd]; - + TagInfoMap::const_iterator ps6Pos = ps6IFD.begin(); TagInfoMap::const_iterator ps6End = ps6IFD.end(); - + for ( ; ps6Pos != ps6End; ++ps6Pos ) { - + // Copy buried tags to the master IFD if they don't already exist there. - + const TagInfo& ps6Tag = ps6Pos->second; - + if ( this->FindTagInIFD ( ifd, ps6Tag.id ) != 0 ) continue; // Keep existing master tags. if ( needsFlipping && (ps6Tag.id == 37500) ) continue; // Don't copy an unflipped MakerNote. if ( (ps6Tag.id == kTIFF_ExifIFDPointer) || // Skip the tags that are explicit offsets. (ps6Tag.id == kTIFF_GPSInfoIFDPointer) || (ps6Tag.id == kTIFF_JPEGInterchangeFormat) || (ps6Tag.id == kTIFF_InteroperabilityIFDPointer) ) continue; - + void* voidPtr = this->CopyTagToMasterIFD ( ps6Tag, masterIFD ); - + if ( needsFlipping ) { switch ( ps6Tag.type ) { - + case kTIFF_ByteType: case kTIFF_SByteType: case kTIFF_ASCIIType: // Nothing more to do. break; - + case kTIFF_ShortType: case kTIFF_SShortType: { @@ -1117,7 +1048,7 @@ void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XM for ( size_t i = ps6Tag.count; i > 0; --i, ++u16Ptr ) Flip2 ( u16Ptr ); } break; - + case kTIFF_LongType: case kTIFF_SLongType: case kTIFF_FloatType: @@ -1126,7 +1057,7 @@ void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XM for ( size_t i = ps6Tag.count; i > 0; --i, ++u32Ptr ) Flip4 ( u32Ptr ); } break; - + case kTIFF_RationalType: case kTIFF_SRationalType: { @@ -1134,14 +1065,14 @@ void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XM for ( size_t i = (2 * ps6Tag.count); i > 0; --i, ++ratPtr ) Flip4 ( ratPtr ); } break; - + case kTIFF_DoubleType: { XMP_Uns64* u64Ptr = (XMP_Uns64*)voidPtr; for ( size_t i = ps6Tag.count; i > 0; --i, ++u64Ptr ) Flip8 ( u64Ptr ); } break; - + case kTIFF_UndefinedType: // Fix up the few kinds of special tables that Exif 2.2 defines. ok = true; // Keep everything that isn't a special table. @@ -1154,20 +1085,92 @@ void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XM } if ( ! ok ) this->DeleteTag ( ifd, ps6Tag.id ); break; - + default: // ? XMP_Throw ( "Unexpected tag type", kXMPErr_InternalFailure ); this->DeleteTag ( ifd, ps6Tag.id ); break; - + } } - + } - + } // TIFF_FileWriter::ProcessPShop6IFD // ================================================================================================= +// TIFF_FileWriter::PreflightIFDLinkage +// ==================================== +// +// Preflight special cases for the linkage between IFDs. Three of the IFDs are found through an +// explicit tag, the Exif, GPS, and Interop IFDs. The presence or absence of those IFDs affects the +// presence or absence of the linkage tag, which can affect the IFD containing the linkage tag. The +// thumbnail IFD is chained from the primary IFD, so if the thumbnail IFD is present we make sure +// that the primary IFD isn't empty. + +void TIFF_FileWriter::PreflightIFDLinkage() +{ + + // Do the tag-linked IFDs bottom up, Interop then GPS then Exif. + + if ( this->containedIFDs[kTIFF_InteropIFD].tagMap.empty() ) { + this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); + } else if ( ! this->GetTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0 ) ) { + this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0xABADABAD ); + } + + if ( this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.empty() ) { + this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer ); + } else if ( ! this->GetTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0 ) ) { + this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0xABADABAD ); + } + + if ( this->containedIFDs[kTIFF_ExifIFD].tagMap.empty() ) { + this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); + } else if ( ! this->GetTag ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0 ) ) { + this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0xABADABAD ); + } + + // Make sure that the primary IFD is not empty if the thumbnail IFD is not empty. + + if ( this->containedIFDs[kTIFF_PrimaryIFD].tagMap.empty() && + (! this->containedIFDs[kTIFF_TNailIFD].tagMap.empty()) ) { + this->SetTag_Short ( kTIFF_PrimaryIFD, kTIFF_ResolutionUnit, 2 ); // Set Resolution unit to inches. + } + +} // TIFF_FileWriter::PreflightIFDLinkage + +// ================================================================================================= +// TIFF_FileWriter::DetermineVisibleLength +// ======================================= + +XMP_Uns32 TIFF_FileWriter::DetermineVisibleLength() +{ + XMP_Uns32 visibleLength = 8; // Start with the TIFF header size. + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); + size_t tagCount = ifdInfo.tagMap.size(); + if ( tagCount == 0 ) continue; + + visibleLength += (XMP_Uns32)( 6 + (12 * tagCount) ); + + InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); + InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); + + for ( ; tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & currTag ( tagPos->second ); + if ( currTag.dataLen > 4 ) visibleLength += ((currTag.dataLen + 1) & 0xFFFFFFFE); // ! Round to even lengths. + } + + } + + return visibleLength; + +} // TIFF_FileWriter::DetermineVisibleLength + +// ================================================================================================= // TIFF_FileWriter::DetermineAppendInfo // ==================================== @@ -1182,7 +1185,7 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, { XMP_Uns32 appendedLength = 0; XMP_Assert ( (appendedOrigin & 1) == 0 ); // Make sure it is even. - + #if Trace_DetermineAppendInfo { printf ( "\nEntering TIFF_FileWriter::DetermineAppendInfo%s\n", (appendAll ? ", append all" : "") ); @@ -1208,59 +1211,56 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, printf ( "\n" ); } #endif - + // Determine which of the IFDs will be appended. If the Exif, GPS, or Interoperability IFDs are // appended, set dummy values for their offsets in the "owning" IFD. This must be done first // since this might cause the owning IFD to grow. - + if ( ! appendAll ) { for ( int i = 0; i < kTIFF_KnownIFDCount ;++i ) appendedIFDs[i] = false; } else { for ( int i = 0; i < kTIFF_KnownIFDCount ;++i ) appendedIFDs[i] = (this->containedIFDs[i].tagMap.size() > 0); } - + appendedIFDs[kTIFF_InteropIFD] |= (this->containedIFDs[kTIFF_InteropIFD].origCount < this->containedIFDs[kTIFF_InteropIFD].tagMap.size()); if ( appendedIFDs[kTIFF_InteropIFD] ) { this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0xABADABAD ); } - + appendedIFDs[kTIFF_GPSInfoIFD] |= (this->containedIFDs[kTIFF_GPSInfoIFD].origCount < this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.size()); if ( appendedIFDs[kTIFF_GPSInfoIFD] ) { this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0xABADABAD ); } - + appendedIFDs[kTIFF_ExifIFD] |= (this->containedIFDs[kTIFF_ExifIFD].origCount < this->containedIFDs[kTIFF_ExifIFD].tagMap.size()); if ( appendedIFDs[kTIFF_ExifIFD] ) { this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0xABADABAD ); } - - appendedIFDs[kTIFF_TNailIFD] |= (this->containedIFDs[kTIFF_TNailIFD].origCount < - this->containedIFDs[kTIFF_TNailIFD].tagMap.size()); - + appendedIFDs[kTIFF_PrimaryIFD] |= (this->containedIFDs[kTIFF_PrimaryIFD].origCount < this->containedIFDs[kTIFF_PrimaryIFD].tagMap.size()); // The appended data (if any) will be a sequence of an IFD followed by its large values. // Determine the new offsets for the appended IFDs and tag values, and the total amount of - // appended stuff. - + // appended stuff. + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount ;++ifd ) { - + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); size_t tagCount = ifdInfo.tagMap.size(); if ( ! (appendAll | ifdInfo.changed) ) continue; if ( tagCount == 0 ) continue; - + newIFDOffsets[ifd] = ifdInfo.origIFDOffset; if ( appendedIFDs[ifd] ) { newIFDOffsets[ifd] = appendedOrigin + appendedLength; appendedLength += (XMP_Uns32)( 6 + (12 * tagCount) ); } - + InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); @@ -1277,11 +1277,11 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, } } - + } - + // If the Exif, GPS, or Interoperability IFDs get appended, update the tag values for their new offsets. - + if ( appendedIFDs[kTIFF_ExifIFD] ) { this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, newIFDOffsets[kTIFF_ExifIFD] ); } @@ -1291,7 +1291,7 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, if ( appendedIFDs[kTIFF_InteropIFD] ) { this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, newIFDOffsets[kTIFF_InteropIFD] ); } - + #if Trace_DetermineAppendInfo { printf ( "Exiting TIFF_FileWriter::DetermineAppendInfo\n" ); @@ -1320,9 +1320,9 @@ XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, printf ( "\n" ); } #endif - + return appendedLength; - + } // TIFF_FileWriter::DetermineAppendInfo // ================================================================================================= @@ -1354,7 +1354,7 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n // Allocate the new block of memory for the full stream. Copy the original stream. Write the // modified IFDs and values. Finally rebuild the internal IFD info and tag map. - + XMP_Uns32 newLength = appendedOrigin + appendedLength; XMP_Uns8* newStream = (XMP_Uns8*) malloc ( newLength + extraSpace ); if ( newStream == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); @@ -1364,16 +1364,16 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n XMP_Assert ( appendedOrigin == (this->tiffLength + 1) ); newStream[this->tiffLength] = 0; // Clear the pad byte. } - + try { // We might get exceptions from the next part and must delete newStream on the way out. - + // Write the modified IFDs and values. Rewrite the full IFD from scratch to make sure the // tags are now unique and sorted. Copy large changed values to their appropriate location. - + XMP_Uns32 appendedOffset = appendedOrigin; - + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { - + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); size_t tagCount = ifdInfo.tagMap.size(); @@ -1381,12 +1381,12 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n if ( tagCount == 0 ) continue; XMP_Uns8* ifdPtr = newStream + newIFDOffsets[ifd]; - + if ( appendedIFDs[ifd] ) { XMP_Assert ( newIFDOffsets[ifd] == appendedOffset ); appendedOffset += (XMP_Uns32)( 6 + (12 * tagCount) ); } - + this->PutUns16 ( (XMP_Uns16)tagCount, ifdPtr ); ifdPtr += 2; @@ -1426,67 +1426,31 @@ void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* n ifdPtr += 4; } - + this->PutUns32 ( ifdInfo.origNextIFD, ifdPtr ); ifdPtr += 4; - + } - + XMP_Assert ( appendedOffset == newLength ); - + // Back fill the offsets for the primary and thumnbail IFDs, if they are now appended. - + if ( appendedIFDs[kTIFF_PrimaryIFD] ) { this->PutUns32 ( newIFDOffsets[kTIFF_PrimaryIFD], (newStream + 4) ); } - - if ( appendedIFDs[kTIFF_TNailIFD] ) { - size_t primaryIFDCount = this->containedIFDs[kTIFF_PrimaryIFD].tagMap.size(); - XMP_Uns32 tnailRefOffset = newIFDOffsets[kTIFF_PrimaryIFD] + 2 + (12 * (XMP_Uns32)primaryIFDCount); - this->PutUns32 ( newIFDOffsets[kTIFF_TNailIFD], (newStream + tnailRefOffset) ); - } - + } catch ( ... ) { - + free ( newStream ); throw; - + } - + *newStream_out = newStream; *newLength_out = newLength; - -} // TIFF_FileWriter::UpdateMemByAppend -// ================================================================================================= -// TIFF_FileWriter::DetermineVisibleLength -// ======================================= - -XMP_Uns32 TIFF_FileWriter::DetermineVisibleLength() -{ - XMP_Uns32 visibleLength = 8; // Start with the TIFF header size. - - for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { - - InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); - size_t tagCount = ifdInfo.tagMap.size(); - if ( tagCount == 0 ) continue; - - visibleLength += (XMP_Uns32)( 6 + (12 * tagCount) ); - - InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); - InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); - - for ( ; tagPos != tagEnd; ++tagPos ) { - InternalTagInfo & currTag ( tagPos->second ); - if ( currTag.dataLen > 4 ) visibleLength += ((currTag.dataLen + 1) & 0xFFFFFFFE); // ! Round to even lengths. - } - - } - - return visibleLength; - -} // TIFF_FileWriter::DetermineVisibleLength +} // TIFF_FileWriter::UpdateMemByAppend // ================================================================================================= // TIFF_FileWriter::UpdateMemByRewrite @@ -1582,54 +1546,39 @@ static const SimpleHiddenContentInfo kSimpleHiddenContentInfo [kSimpleHiddenCont // ------------------------------------------------------------------------------------------------- -void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out ) +void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out ) { const InternalTagInfo* tagInfo; - + // Check for tags that we don't tolerate because they have data we can't (or refuse to) find. - + for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { for ( int i = 0; kNoGoTags[i] != 0xFFFF; ++i ) { tagInfo = this->FindTagInIFD ( ifd, kNoGoTags[i] ); if ( tagInfo != 0 ) XMP_Throw ( "Tag not tolerated for TIFF rewrite", kXMPErr_Unimplemented ); } } - - // Delete unwanted tags. - + + // Delete unwanted tags. + for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { for ( int i = 0; kBanishedTags[i] != 0xFFFF; ++i ) { this->DeleteTag ( ifd, kBanishedTags[i] ); } } - - // Make sure the "pointer" tags for the Exif, GPS, and Interop IFDs exist. The order is - // important, adding the Interop pointer can cause the Exif IFD to exist. - - if ( ! this->containedIFDs[kTIFF_InteropIFD].tagMap.empty() ) { - this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0xABADABAD ); - } - - if ( ! this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.empty() ) { - this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0xABADABAD ); - } - - if ( ! this->containedIFDs[kTIFF_ExifIFD].tagMap.empty() ) { - this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0xABADABAD ); - } // Determine the offsets and additional size for the hidden offset content. Set the offset tags // to the new offset. - + XMP_Uns32 hiddenContentLength = 0; XMP_Uns32 hiddenContentOrigin = this->DetermineVisibleLength(); - + SimpleHiddenContentLocations hiddenLocations [kSimpleHiddenContentCount]; - + for ( int i = 0; i < kSimpleHiddenContentCount; ++i ) { const SimpleHiddenContentInfo & hiddenInfo ( kSimpleHiddenContentInfo[i] ); - + bool haveLength = this->GetTag_Integer ( hiddenInfo.ifd, hiddenInfo.lengthTag, &hiddenLocations[i].length ); bool haveOffset = this->GetTag_Integer ( hiddenInfo.ifd, hiddenInfo.offsetTag, &hiddenLocations[i].oldOffset ); if ( haveLength != haveOffset ) XMP_Throw ( "Unpaired simple hidden content tag", kXMPErr_BadTIFF ); @@ -1640,7 +1589,7 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* hiddenContentLength += ((hiddenLocations[i].length + 1) & 0xFFFFFFFE); // ! Round up for even offsets. } - + // Save any old memory stream for the content behind hidden offsets. Setup a bare TIFF header. XMP_Uns8* oldStream = this->memStream; @@ -1652,20 +1601,20 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* bareTIFF[0] = 0x49; bareTIFF[1] = 0x49; bareTIFF[2] = 0x2A; bareTIFF[3] = 0x00; } *((XMP_Uns32*)&bareTIFF[4]) = 0; - + this->memStream = &bareTIFF[0]; this->tiffLength = sizeof ( bareTIFF ); this->ownedStream = false; // Call UpdateMemByAppend to write the new stream, telling it to append everything. - + this->UpdateMemByAppend ( newStream_out, newLength_out, true, hiddenContentLength ); // Copy the hidden content and update the output stream length; XMP_Assert ( *newLength_out == hiddenContentOrigin ); *newLength_out += hiddenContentLength; - + for ( int i = 0; i < kSimpleHiddenContentCount; ++i ) { if ( hiddenLocations[i].length == 0 ) continue; @@ -1675,7 +1624,7 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* memcpy ( destPtr, srcPtr, hiddenLocations[i].length ); // AUDIT: Safe copy, not user data, computed length. } - + } // TIFF_FileWriter::UpdateMemByRewrite // ================================================================================================= @@ -1692,15 +1641,17 @@ void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* // will discard any MakerNote tags and risks breaking offsets that are hidden. This can be necessary // though to try to make the TIFF fit in a JPEG file. -XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStream /* = false */ ) +XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStream /* = false */ ) { if ( this->fileParsed ) XMP_Throw ( "Not memory based", kXMPErr_EnforceFailure ); - + if ( ! this->changed ) { if ( dataPtr != 0 ) *dataPtr = this->memStream; return this->tiffLength; } - + + this->PreflightIFDLinkage(); + bool nowEmpty = true; for ( size_t i = 0; i < kTIFF_KnownIFDCount; ++i ) { if ( ! this->containedIFDs[i].tagMap.empty() ) { @@ -1708,39 +1659,39 @@ XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStr break; } } - + XMP_Uns8* newStream = 0; XMP_Uns32 newLength = 0; - + if ( nowEmpty ) { - + this->DeleteExistingInfo(); // Prepare for an empty reparse. - + } else { if ( this->tiffLength == 0 ) { // ! An empty parse does set this->memParsed. condenseStream = true; // Makes "conjured" TIFF take the full rewrite path. } - + if ( condenseStream ) this->changed = true; // A prior regular call would have cleared this->changed. - + if ( condenseStream ) { this->UpdateMemByRewrite ( &newStream, &newLength ); } else { this->UpdateMemByAppend ( &newStream, &newLength ); } - + } - + // Parse the revised stream. This is the cleanest way to rebuild the tag map. - + this->ParseMemoryStream ( newStream, newLength, kDoNotCopyData ); XMP_Assert ( this->tiffLength == newLength ); this->ownedStream = (newLength > 0); // ! We really do own the new stream, if not empty. - + if ( dataPtr != 0 ) *dataPtr = this->memStream; return newLength; - + } // TIFF_FileWriter::UpdateMemoryStream // ================================================================================================= @@ -1769,17 +1720,17 @@ XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStr #define Trace_UpdateFileStream 0 #endif -void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) +void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) { if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure ); if ( ! this->changed ) return; - + XMP_Int64 origDataLength = LFA_Measure ( fileRef ); if ( (origDataLength >> 32) != 0 ) XMP_Throw ( "TIFF files can't exceed 4GB", kXMPErr_BadTIFF ); - + bool appendedIFDs[kTIFF_KnownIFDCount]; XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount]; - + #if Trace_UpdateFileStream printf ( "\nStarting update of TIFF file stream\n" ); #endif @@ -1791,15 +1742,17 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) LFA_Write ( fileRef, "\0", 1 ); } + this->PreflightIFDLinkage(); + XMP_Uns32 appendedLength = DetermineAppendInfo ( appendedOrigin, appendedIFDs, newIFDOffsets ); if ( appendedLength > (0xFFFFFFFFUL - appendedOrigin) ) XMP_Throw ( "TIFF files can't exceed 4GB", kXMPErr_BadTIFF ); // Do the in-place update for the IFDs and tag values that fit. This part does separate seeks // and writes for the IFDs and values. Things to be updated can be anywhere in the file. - + // *** This might benefit from a map of the in-place updates. This would allow use of a possibly // *** more efficient sequential I/O model. Could even incorporate the safe update file copying. - + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; @@ -1807,7 +1760,7 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) // In order to get a little bit of locality, write the IFD first then the changed tags that // have large values and fit in-place. - + if ( ! appendedIFDs[ifd] ) { #if Trace_UpdateFileStream printf ( " Updating IFD %d in-place at offset %d (0x%X)\n", ifd, thisIFD.origIFDOffset, thisIFD.origIFDOffset ); @@ -1815,10 +1768,10 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) LFA_Seek ( fileRef, thisIFD.origIFDOffset, SEEK_SET ); this->WriteFileIFD ( fileRef, thisIFD ); } - + InternalTagMap::iterator tagPos; InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); - + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { InternalTagInfo & thisTag = tagPos->second; if ( (! thisTag.changed) || (thisTag.dataLen <= 4) || (thisTag.dataLen > thisTag.origDataLen) ) continue; @@ -1835,12 +1788,12 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) XMP_Int64 fileEnd = LFA_Seek ( fileRef, 0, SEEK_END ); XMP_Assert ( fileEnd == appendedOrigin ); - + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; if ( ! thisIFD.changed ) continue; - + if ( appendedIFDs[ifd] ) { #if Trace_UpdateFileStream printf ( " Updating IFD %d by append at offset %d (0x%X)\n", ifd, newIFDOffsets[ifd], newIFDOffsets[ifd] ); @@ -1848,10 +1801,10 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) XMP_Assert ( newIFDOffsets[ifd] == LFA_Measure(fileRef) ); this->WriteFileIFD ( fileRef, thisIFD ); } - + InternalTagMap::iterator tagPos; InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); - + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { InternalTagInfo & thisTag = tagPos->second; if ( (! thisTag.changed) || (thisTag.dataLen <= 4) || (thisTag.dataLen <= thisTag.origDataLen) ) continue; @@ -1866,10 +1819,10 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) } - // Back-fill the offsets for the primary and thumnbail IFDs, if they are now appended. - + // Back-fill the offset for the primary IFD, if it is now appended. + XMP_Uns32 newOffset; - + if ( appendedIFDs[kTIFF_PrimaryIFD] ) { this->PutUns32 ( newIFDOffsets[kTIFF_PrimaryIFD], &newOffset ); #if TraceUpdateFileStream @@ -1878,30 +1831,10 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) LFA_Seek ( fileRef, 4, SEEK_SET ); LFA_Write ( fileRef, &newOffset, 4 ); } - - InternalIFDInfo & primaryIFD = this->containedIFDs[kTIFF_PrimaryIFD]; - InternalIFDInfo & tnailIFD = this->containedIFDs[kTIFF_TNailIFD]; - - if ( appendedIFDs[kTIFF_TNailIFD] && (primaryIFD.origNextIFD == tnailIFD.origIFDOffset) ) { - - size_t primaryIFDCount = primaryIFD.tagMap.size(); - XMP_Uns32 tnailRefOffset = newIFDOffsets[kTIFF_PrimaryIFD] + 2 + (12 * (XMP_Uns32)primaryIFDCount); - - this->PutUns32 ( newIFDOffsets[kTIFF_TNailIFD], &newOffset ); - #if TraceUpdateFileStream - printf ( " Back-filling offset of thumbnail IFD, offset at %d (0x%X), pointing to %d (0x%X)\n", - tnailRefOffset, tnailRefOffset, newOffset, newOffset ); - #endif - LFA_Seek ( fileRef, tnailRefOffset, SEEK_SET ); - LFA_Write ( fileRef, &newOffset, 4 ); - - primaryIFD.origNextIFD = newIFDOffsets[kTIFF_TNailIFD]; // ! Ought to be below, easier here. - } - // Reset the changed flags and original length/offset values. This simulates a reparse of the // updated file. - + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; @@ -1910,10 +1843,10 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) thisIFD.changed = false; thisIFD.origCount = (XMP_Uns16)( thisIFD.tagMap.size() ); thisIFD.origIFDOffset = newIFDOffsets[ifd]; - + InternalTagMap::iterator tagPos; InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); - + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { InternalTagInfo & thisTag = tagPos->second; if ( ! thisTag.changed ) continue; @@ -1926,7 +1859,7 @@ void TIFF_FileWriter::UpdateFileStream ( LFA_FileRef fileRef ) this->tiffLength = (XMP_Uns32) LFA_Measure ( fileRef ); LFA_Seek ( fileRef, 0, SEEK_END ); // Can't hurt. - + #if Trace_UpdateFileStream printf ( "\nFinished update of TIFF file stream\n" ); #endif @@ -1942,7 +1875,7 @@ void TIFF_FileWriter::WriteFileIFD ( LFA_FileRef fileRef, InternalIFDInfo & this XMP_Uns16 tagCount; this->PutUns16 ( (XMP_Uns16)thisIFD.tagMap.size(), &tagCount ); LFA_Write ( fileRef, &tagCount, 2 ); - + InternalTagMap::iterator tagPos; InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); @@ -1960,7 +1893,7 @@ void TIFF_FileWriter::WriteFileIFD ( LFA_FileRef fileRef, InternalIFDInfo & this XMP_Assert ( sizeof(ifdEntry) == 12 ); } - + XMP_Uns32 nextIFD; this->PutUns32 ( thisIFD.origNextIFD, &nextIFD ); LFA_Write ( fileRef, &nextIFD, 4 ); diff --git a/source/XMPFiles/FormatSupport/TIFF_MemoryReader.cpp b/source/XMPFiles/FormatSupport/TIFF_MemoryReader.cpp index 4ca9cac..316cea0 100644 --- a/source/XMPFiles/FormatSupport/TIFF_MemoryReader.cpp +++ b/source/XMPFiles/FormatSupport/TIFF_MemoryReader.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -14,7 +14,7 @@ /// \brief Implementation of the memory-based read-only TIFF_Manager. /// /// The read-only forms of TIFF_Manager are derived from TIFF_Reader. The GetTag methods are common -/// implementations in TIFF_Reader. The parsing code is different in the TIFF_MemoryReader and +/// implementations in TIFF_Reader. The parsing code is different in the TIFF_MemoryReader and /// TIFF_FileReader constructors. There are also separate destructors to release captured info. /// /// The read-only implementations use runtime data that is simple tweaks on the stored form. The @@ -52,9 +52,9 @@ void TIFF_MemoryReader::SortIFD ( TweakedIFDInfo* thisIFD ) XMP_Uns16 tagCount = thisIFD->count; TweakedIFDEntry* ifdEntries = thisIFD->entries; XMP_Uns16 prevTag = ifdEntries[0].id; - + for ( size_t i = 1; i < tagCount; ++i ) { - + XMP_Uns16 thisTag = ifdEntries[i].id; if ( thisTag > prevTag ) { @@ -76,7 +76,7 @@ void TIFF_MemoryReader::SortIFD ( TweakedIFDInfo* thisIFD ) for ( j = (XMP_Int32)i-1; j >= 0; --j ) { if ( ifdEntries[j].id <= thisTag ) break; } - + if ( (j >= 0) && (ifdEntries[j].id == thisTag) ) { // Out of order duplicate, move it to position j, move the tail of the array up. @@ -96,11 +96,11 @@ void TIFF_MemoryReader::SortIFD ( TweakedIFDInfo* thisIFD ) } } - + } - + thisIFD->count = tagCount; // Save the final count. - + } // TIFF_MemoryReader::SortIFD // ================================================================================================= @@ -111,15 +111,16 @@ bool TIFF_MemoryReader::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const { if ( ifd > kTIFF_LastRealIFD ) XMP_Throw ( "Invalid IFD requested", kXMPErr_InternalFailure ); const TweakedIFDInfo* thisIFD = &containedIFDs[ifd]; - + if ( ifdMap != 0 ) ifdMap->clear(); if ( thisIFD->count == 0 ) return false; - + if ( ifdMap != 0 ) { - + for ( size_t i = 0; i < thisIFD->count; ++i ) { TweakedIFDEntry* thisTag = &(thisIFD->entries[i]); + if ( (thisTag->type < kTIFF_ByteType) || (thisTag->type > kTIFF_LastType) ) continue; // Bad type, skip this tag. TagInfo info ( thisTag->id, thisTag->type, 0, 0, thisTag->bytes ); info.count = info.dataLen / (XMP_Uns32)kTIFF_TypeSizes[info.type]; @@ -128,9 +129,9 @@ bool TIFF_MemoryReader::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const (*ifdMap)[info.id] = info; } - + } - + return true; } // TIFF_MemoryReader::GetIFD @@ -144,23 +145,23 @@ const TIFF_MemoryReader::TweakedIFDEntry* TIFF_MemoryReader::FindTagInIFD ( XMP_ if ( ifd == kTIFF_KnownIFD ) { // ... lookup the tag in the known tag map } - + if ( ifd > kTIFF_LastRealIFD ) XMP_Throw ( "Invalid IFD requested", kXMPErr_InternalFailure ); const TweakedIFDInfo* thisIFD = &containedIFDs[ifd]; - + if ( thisIFD->count == 0 ) return 0; - + XMP_Uns32 spanLength = thisIFD->count; const TweakedIFDEntry* spanBegin = &(thisIFD->entries[0]); - + while ( spanLength > 1 ) { XMP_Uns32 halfLength = spanLength >> 1; // Since spanLength > 1, halfLength > 0. const TweakedIFDEntry* spanMiddle = spanBegin + halfLength; - + // There are halfLength entries below spanMiddle, then the spanMiddle entry, then // spanLength-halfLength-1 entries above spanMiddle (which can be none). - + if ( spanMiddle->id == id ) { spanBegin = spanMiddle; break; @@ -170,9 +171,9 @@ const TIFF_MemoryReader::TweakedIFDEntry* TIFF_MemoryReader::FindTagInIFD ( XMP_ spanBegin = spanMiddle; // Keep a valid spanBegin for the return check, don't use spanMiddle+1. spanLength -= halfLength; } - + } - + if ( spanBegin->id != id ) spanBegin = 0; return spanBegin; @@ -186,11 +187,11 @@ XMP_Uns32 TIFF_MemoryReader::GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const { const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return 0; - + XMP_Uns8 * valuePtr = (XMP_Uns8*) this->GetDataPtr ( thisTag ); - + return (XMP_Uns32)(valuePtr - this->tiffStream); // ! TIFF streams can't exceed 4GB. - + } // TIFF_MemoryReader::GetValueOffset // ================================================================================================= @@ -201,20 +202,21 @@ bool TIFF_MemoryReader::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) con { const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; - + if ( (thisTag->type < kTIFF_ByteType) || (thisTag->type > kTIFF_LastType) ) return false; // Bad type, skip this tag. + if ( info != 0 ) { info->id = thisTag->id; info->type = thisTag->type; info->count = thisTag->bytes / (XMP_Uns32)kTIFF_TypeSizes[thisTag->type]; info->dataLen = thisTag->bytes; - + info->dataPtr = this->GetDataPtr ( thisTag ); } - + return true; - + } // TIFF_MemoryReader::GetTag // ================================================================================================= @@ -225,7 +227,7 @@ bool TIFF_MemoryReader::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* { const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; - + if ( data != 0 ) { if ( thisTag->type == kTIFF_ShortType ) { if ( thisTag->bytes != 2 ) return false; // Wrong count. @@ -237,7 +239,7 @@ bool TIFF_MemoryReader::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* return false; } } - + return true; } // TIFF_MemoryReader::GetTag_Integer @@ -251,11 +253,11 @@ bool TIFF_MemoryReader::GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_ByteType) || (thisTag->bytes != 1) ) return false; - + if ( data != 0 ) { *data = * ( (XMP_Uns8*) this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_Byte @@ -269,11 +271,11 @@ bool TIFF_MemoryReader::GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* dat const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SByteType) || (thisTag->bytes != 1) ) return false; - + if ( data != 0 ) { *data = * ( (XMP_Int8*) this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_SByte @@ -287,11 +289,11 @@ bool TIFF_MemoryReader::GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* da const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_ShortType) || (thisTag->bytes != 2) ) return false; - + if ( data != 0 ) { *data = this->GetUns16 ( this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_Short @@ -305,11 +307,11 @@ bool TIFF_MemoryReader::GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* d const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SShortType) || (thisTag->bytes != 2) ) return false; - + if ( data != 0 ) { *data = (XMP_Int16) this->GetUns16 ( this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_SShort @@ -323,11 +325,11 @@ bool TIFF_MemoryReader::GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* dat const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_LongType) || (thisTag->bytes != 4) ) return false; - + if ( data != 0 ) { *data = this->GetUns32 ( this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_Long @@ -341,11 +343,11 @@ bool TIFF_MemoryReader::GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* da const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SLongType) || (thisTag->bytes != 4) ) return false; - + if ( data != 0 ) { *data = (XMP_Int32) this->GetUns32 ( this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_SLong @@ -359,13 +361,13 @@ bool TIFF_MemoryReader::GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_RationalType) || (thisTag->bytes != 8) ) return false; - + if ( data != 0 ) { XMP_Uns32* dataPtr = (XMP_Uns32*) this->GetDataPtr ( thisTag ); data->num = this->GetUns32 ( dataPtr ); data->denom = this->GetUns32 ( dataPtr+1 ); } - + return true; } // TIFF_MemoryReader::GetTag_Rational @@ -379,13 +381,13 @@ bool TIFF_MemoryReader::GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_SRationalType) || (thisTag->bytes != 8) ) return false; - + if ( data != 0 ) { XMP_Uns32* dataPtr = (XMP_Uns32*) this->GetDataPtr ( thisTag ); data->num = (XMP_Int32) this->GetUns32 ( dataPtr ); data->denom = (XMP_Int32) this->GetUns32 ( dataPtr+1 ); } - + return true; } // TIFF_MemoryReader::GetTag_SRational @@ -399,11 +401,11 @@ bool TIFF_MemoryReader::GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_FloatType) || (thisTag->bytes != 4) ) return false; - + if ( data != 0 ) { *data = this->GetFloat ( this->GetDataPtr ( thisTag ) ); } - + return true; } // TIFF_MemoryReader::GetTag_Float @@ -417,12 +419,12 @@ bool TIFF_MemoryReader::GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( (thisTag->type != kTIFF_DoubleType) || (thisTag->bytes != 8) ) return false; - + if ( data != 0 ) { double* dataPtr = (double*) this->GetDataPtr ( thisTag ); *data = this->GetDouble ( dataPtr ); } - + return true; } // TIFF_MemoryReader::GetTag_Double @@ -436,13 +438,13 @@ bool TIFF_MemoryReader::GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( thisTag->type != kTIFF_ASCIIType ) return false; - + if ( dataPtr != 0 ) { *dataPtr = (XMP_StringPtr) this->GetDataPtr ( thisTag ); } - + if ( dataLen != 0 ) *dataLen = thisTag->bytes; - + return true; } // TIFF_MemoryReader::GetTag_ASCII @@ -456,9 +458,9 @@ bool TIFF_MemoryReader::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std:: const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); if ( thisTag == 0 ) return false; if ( thisTag->type != kTIFF_UndefinedType ) return false; - + if ( utf8Str == 0 ) return true; // Return true if the converted string is not wanted. - + bool ok = this->DecodeString ( this->GetDataPtr ( thisTag ), thisTag->bytes, utf8Str ); return ok; @@ -473,21 +475,21 @@ bool TIFF_MemoryReader::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std:: void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) { // Get rid of any current TIFF. - + if ( this->ownedStream ) free ( this->tiffStream ); this->ownedStream = false; this->tiffStream = 0; this->tiffLength = 0; - + for ( size_t i = 0; i < kTIFF_KnownIFDCount; ++i ) { this->containedIFDs[i].count = 0; this->containedIFDs[i].entries = 0; } - + if ( length == 0 ) return; // Allocate space for the full in-memory stream and copy it. - + if ( ! copyData ) { XMP_Assert ( ! this->ownedStream ); this->tiffStream = (XMP_Uns8*) data; @@ -500,13 +502,16 @@ void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, } this->tiffLength = length; - + // Find and process the primary, Exif, GPS, and Interoperability IFDs. - + XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( this->tiffStream, length ); XMP_Uns32 tnailIFDOffset = 0; - + if ( primaryIFDOffset != 0 ) tnailIFDOffset = this->ProcessOneIFD ( primaryIFDOffset, kTIFF_PrimaryIFD ); + + // ! Need the thumbnail IFD for checking full Exif APP1 in some JPEG files! + if ( tnailIFDOffset != 0 ) (void) this->ProcessOneIFD ( tnailIFDOffset, kTIFF_TNailIFD ); const TweakedIFDEntry* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->bytes == 4) ) { @@ -525,18 +530,6 @@ void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, XMP_Uns32 interopOffset = this->GetUns32 ( &interopIFDTag->dataOrPos ); (void) this->ProcessOneIFD ( interopOffset, kTIFF_InteropIFD ); } - - // Process the thumbnail IFD. We only do this for Exif-compliant TIFF streams. Extract the - // JPEG thumbnail image pointer (tag 513) for later use by GetTNailInfo. - - if ( (tnailIFDOffset != 0) && (this->containedIFDs[kTIFF_ExifIFD].count > 0) ) { - (void) this->ProcessOneIFD ( tnailIFDOffset, kTIFF_TNailIFD ); - const TweakedIFDEntry* jpegInfo = FindTagInIFD ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat ); - if ( jpegInfo != 0 ) { - XMP_Uns32 tnailImageOffset = this->GetUns32 ( &jpegInfo->dataOrPos ); - this->jpegTNailPtr = (XMP_Uns8*)this->tiffStream + tnailImageOffset; - } - } } // TIFF_MemoryReader::ParseMemoryStream @@ -547,11 +540,11 @@ void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) { TweakedIFDInfo& ifdInfo = this->containedIFDs[ifd]; - + if ( (ifdOffset < 8) || (ifdOffset > (this->tiffLength - kEmptyIFDLength)) ) { XMP_Throw ( "Bad IFD offset", kXMPErr_BadTIFF ); } - + XMP_Uns8* ifdPtr = this->tiffStream + ifdOffset; XMP_Uns16 ifdCount = this->GetUns16 ( ifdPtr ); TweakedIFDEntry* ifdEntries = (TweakedIFDEntry*)(ifdPtr+2); @@ -561,11 +554,11 @@ XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) ifdInfo.count = ifdCount; ifdInfo.entries = ifdEntries; - + XMP_Int32 prevTag = -1; // ! The GPS IFD has a tag 0, so we need a signed initial value. bool needsSorting = false; for ( size_t i = 0; i < ifdCount; ++i ) { - + TweakedIFDEntry* thisEntry = &ifdEntries[i]; // Tweak the IFD entry to be more useful. if ( ! this->nativeEndian ) { @@ -573,7 +566,7 @@ XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) Flip2 ( &thisEntry->type ); Flip4 ( &thisEntry->bytes ); } - + if ( thisEntry->id <= prevTag ) needsSorting = true; prevTag = thisEntry->id; @@ -586,12 +579,12 @@ XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) } } - + ifdPtr += (2 + ifdCount*12); XMP_Uns32 nextIFDOffset = this->GetUns32 ( ifdPtr ); - + if ( needsSorting ) SortIFD ( &ifdInfo ); // ! Don't perturb the ifdCount used to find the next IFD offset. - + return nextIFDOffset; } // TIFF_MemoryReader::ProcessOneIFD diff --git a/source/XMPFiles/FormatSupport/TIFF_Support.cpp b/source/XMPFiles/FormatSupport/TIFF_Support.cpp index 8aecc10..87a96c9 100644 --- a/source/XMPFiles/FormatSupport/TIFF_Support.cpp +++ b/source/XMPFiles/FormatSupport/TIFF_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -25,10 +25,9 @@ static bool sFirstCTor = true; TIFF_Manager::TIFF_Manager() - : bigEndian(false), nativeEndian(false), jpegTNailPtr(0), + : bigEndian(false), nativeEndian(false), GetUns16(0), GetUns32(0), GetFloat(0), GetDouble(0), - PutUns16(0), PutUns32(0), PutFloat(0), PutDouble(0), - xmpHadUserComment(false), xmpHadRelatedSoundFile(false) + PutUns16(0), PutUns32(0), PutFloat(0), PutDouble(0) { if ( sFirstCTor ) { @@ -318,10 +317,20 @@ bool TIFF_Manager::DecodeString ( const void * encodedPtr, size_t encodedLen, st } else if ( *typePtr == 'U' ) { try { + const UTF16Unit * utf16Ptr = (const UTF16Unit *) valuePtr; size_t utf16Len = valueLen >> 1; // The number of UTF-16 storage units, not bytes. - UTF16_to_UTF8 ( utf16Ptr, utf16Len, this->bigEndian, utf8Str ); + if ( utf16Len == 0 ) return false; + bool isBigEndian = this->bigEndian; // Default to stream endian, unless there is a BOM ... + if ( (*utf16Ptr == 0xFEFF) || (*utf16Ptr == 0xFFFE) ) { // Check for an explicit BOM + isBigEndian = (*((XMP_Uns8*)utf16Ptr) == 0xFE); + utf16Ptr += 1; // Don't translate the BOM. + utf16Len -= 1; + if ( utf16Len == 0 ) return false; + } + UTF16_to_UTF8 ( utf16Ptr, utf16Len, isBigEndian, utf8Str ); return true; + } catch ( ... ) { return false; // Ignore the tag if there are conversion errors. } @@ -423,204 +432,3 @@ bool TIFF_Manager::EncodeString ( const std::string& utf8Str, XMP_Uns8 encoding, } // TIFF_Manager::EncodeString // ================================================================================================= -// GetJPEGDimensions -// ================= -// -// Get the internal dimensions for a JPEG compressed image. These are the X (width) and Y (height) -// components of the SOFn marker segment. A 0 value for Y says that the height is in the NL -// component of the DNL marker segment at the end of the first scan. We'll use the first SOF, in -// the case of hierarchical JPEG which has multiple frames. -// -// For this logic a JPEG stream is: -// SOI standalone marker -// Optional marker segments -// First frame: -// SOFn marker segment -// First scan: -// Optional marker segments -// SOS marker segment -// Image data and RST standalone markers -// Optional DNL marker segment -// Optional additional scans -// Optional additional frames -// EOI standalone marker -// -// There is no explicit length for the image data portion of a scan. It ends at the first non-RST -// marker. So we look no further than the first non-RST marker after the first SOS marker segment. -// That is the one and only DNL marker segment, if it exists. Hopefully we stop at the first SOFn. -// -// The first 5 bytes of the SOFn contents are: -// Uns8 - ignored here -// Uns16 - Y, height, big endian -// Uns16 - X, width, big endian -// -// A DNL marker segment contains just 2 bytes of data, the big endian Uns16 number of lines. - -static void GetJPEGDimensions ( const void * jpegStream, size_t jpegLength, XMP_Uns32 * width, XMP_Uns32 * height ) -{ - const XMP_Uns8 * jpegPtr = (const XMP_Uns8 *) jpegStream; - const XMP_Uns8 * jpegEnd = jpegPtr + jpegLength; - - XMP_Uns16 marker, length; - - *width = *height = 0; // Assume the worst. - - marker = GetUns16BE ( jpegPtr ); - if ( marker != 0xFFD8 ) return; // Check for the SOI. - jpegPtr += 2; - - // Scan for the first SOFn marker and extract the Y and X components. - - while ( jpegPtr < jpegEnd ) { - marker = GetUns16BE ( jpegPtr ); - if ( ((marker & 0xFFF0) == 0xFFC0) && - (marker != 0xFFC4) && (marker != 0xFFC8) && (marker != 0xFFCC) ) break; - jpegPtr += 2; - if ( (jpegPtr < jpegEnd) && ((marker & 0xFFF8) != 0xFFD0) && - (marker != 0xFF01) && (marker != 0xFFD8) && (marker != 0xFFD9) ) { - jpegPtr += GetUns16BE ( jpegPtr ); - } - } - - if ( jpegPtr >= jpegEnd ) return; // Ran out of data. - if ( (marker & 0xFFF0) != 0xFFC0 ) return; // Not an SOFn marker. - jpegPtr += 2; - length = GetUns16BE ( jpegPtr ); - if ( length < 7 ) return; // Bad length, the SOFn marker segment is too short. - - *height = GetUns16BE ( jpegPtr+3 ); - *width = GetUns16BE ( jpegPtr+5 ); - if ( *height != 0 ) return; // Done if the Y component is non-zero. - jpegPtr += length; - - // Need to look for a DNL marker segment. Scan for the first SOS marker. - - while ( jpegPtr < jpegEnd ) { - marker = GetUns16BE ( jpegPtr ); - if ( marker == 0xFFDA ) break; - jpegPtr += 2; - if ( (jpegPtr < jpegEnd) && ((marker & 0xFFF8) != 0xFFD0) && - (marker != 0xFF01) && (marker != 0xFFD8) && (marker != 0xFFD9) ) { - jpegPtr += GetUns16BE ( jpegPtr ); - } - } - - if ( jpegPtr >= jpegEnd ) return; // Ran out of data. - if ( marker != 0xFFDA ) return; // Not an SOS marker. - jpegPtr += 2; - length = GetUns16BE ( jpegPtr ); - jpegPtr += length; - - // Now look for a non-RST marker. We're in the image data, must scan one byte at a time. - - while ( jpegPtr < jpegEnd ) { - if ( *jpegPtr != 0xFF ) { - ++jpegPtr; - } else { - marker = GetUns16BE ( jpegPtr ); - if ( (0xFF01 <= marker) && (marker <= 0xFFFE) && ((marker & 0xFFF8) != 0xFFD0) ) break; - jpegPtr += 2; - } - } - - if ( jpegPtr >= jpegEnd ) return; // Ran out of data. - if ( marker != 0xFFDC ) return; // Not a DNL marker. - jpegPtr += 2; - length = GetUns16BE ( jpegPtr ); - if ( length != 4 ) return; // Bad DNL marker segment length. - - *height = GetUns16BE ( jpegPtr+2 ); - -} // GetJPEGDimensions - -// ================================================================================================= -// TIFF_Manager::GetTNailInfo -// ========================== -// -// Gather the info for a native Exif thumbnail, if there is one. We only return full info for a JPEG -// compressed thumbnail. -// - There must be at least 2 top level IFDs, the second is the thumbnail. -// - The Exif IFD must be present. -// - The thumbnail IFD must have tag 259, Compression. -// - A JPEG compressed thumbnail must have tags 513 and 514, JPEGInterchangeFormat and JPEGInterchangeFormatLength. -// -// Tag 259 (Compression) in the thumbnail IFD defines the thumbnail compression scheme. It is 1 for -// uncompressed and 6 for JPEG compressed. If the thumbnail is JPEG compressed, then tag 513 -// (JPEGInterchangeFormat) in the thumbnail IFD is the offset of the thumbnail image stream (to the -// SOI) and tag 514 (JPEGInterchangeFormatLength) is the length of the stream in bytes. Yes, -// another stupid Exif mistake of putting an explicit offset in the TIFF info (type LONG, count 1) -// instead of a properly typed data block! -// -// The full image dimensions for an Exif-compliant compressed JPEG image are in tags 40962 -// (PixelXDimension) and 40963 (PixelYDimension) of the Exif IFD. -// -// The dimensions of an Exif-compliant uncompressed (TIFF) thumbnail are in tags 256 (ImageWidth) -// and 257 (ImageLength) of the thumbnail IFD. The dimensions of an Exif-compliant compressed -// (JPEG) thumbnail are within the JPEG stream of the thumbnail. The JPEG dimensions should be in -// the X (width) and Y (height) components of the SOF marker segment. A 0 value for Y says that the -// height is in the NL component of the DNL marker segment at the end of the first scan. - -bool TIFF_Manager::GetTNailInfo ( XMP_ThumbnailInfo * tnailInfo ) const -{ - bool found; - XMP_Uns16 compression; - - enum { kUncompressedTNail = 1, kJPEGCompressedTNail = 6 }; - - if ( tnailInfo == 0 ) return false; - - // Make sure the required IFDs and tags are present. - - if ( (! this->HasExifIFD()) || (! this->HasThumbnailIFD()) ) return false; - - found = this->GetTag_Short ( kTIFF_TNailIFD, kTIFF_Compression, &compression ); - if ( ! found ) return false; - if ( (compression != kUncompressedTNail) && (compression != kJPEGCompressedTNail) ) return false; - - // Gather the info that depends on the thumbnail format. - - if ( compression == kUncompressedTNail ) { - - // Gather the info for an uncompressed thumbnail. Just the format, width, and height. - - tnailInfo->tnailFormat = kXMP_TIFFTNail; - (void) this->GetTag_Integer ( kTIFF_TNailIFD, kTIFF_ImageWidth, &tnailInfo->tnailWidth ); - (void) this->GetTag_Integer ( kTIFF_TNailIFD, kTIFF_ImageLength, &tnailInfo->tnailHeight ); - - } else { - - // Gather the info for a JPEG compressed thumbnail. The JPEG stream pointer is special, the - // type/count of tag 513 is LONG/1 - thank once again Exif! The pointer was set when parsing - // the TIFF stream. That is when we have to capture the stream for file-based TIFF. - - XMP_Uns32 jpegOffset, jpegLength; - found = this->GetTag_Long ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat, &jpegOffset ); - if ( ! found ) return false; - found = this->GetTag_Long ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormatLength, &jpegLength ); - if ( ! found ) return false; - - XMP_Assert ( this->jpegTNailPtr != 0 ); - - tnailInfo->tnailFormat = kXMP_JPEGTNail; - tnailInfo->tnailImage = this->jpegTNailPtr; - tnailInfo->tnailSize = jpegLength; - - GetJPEGDimensions ( tnailInfo->tnailImage, tnailInfo->tnailSize, - &tnailInfo->tnailWidth, &tnailInfo->tnailHeight ); - - } - - // If we get here there is a thumbnail of some sort. Gether remaining common info. - - (void) this->GetTag_Integer ( kTIFF_ExifIFD, kTIFF_PixelXDimension, &tnailInfo->fullWidth ); - (void) this->GetTag_Integer ( kTIFF_ExifIFD, kTIFF_PixelYDimension, &tnailInfo->fullHeight ); - (void) this->GetTag_Short ( kTIFF_PrimaryIFD, kTIFF_Orientation, &tnailInfo->fullOrientation ); - - found = this->GetTag_Short ( kTIFF_TNailIFD, kTIFF_Orientation, &tnailInfo->tnailOrientation ); - if ( ! found ) tnailInfo->tnailOrientation = tnailInfo->fullOrientation; - - return true; - -} // TIFF_Manager::GetTNailInfo - -// ================================================================================================= diff --git a/source/XMPFiles/FormatSupport/TIFF_Support.hpp b/source/XMPFiles/FormatSupport/TIFF_Support.hpp index 1394b99..94f6198 100644 --- a/source/XMPFiles/FormatSupport/TIFF_Support.hpp +++ b/source/XMPFiles/FormatSupport/TIFF_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2006 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -38,15 +38,12 @@ /// entirely in memory. Think of TIFF_FileWriter as "file-based OR read-write". TIFF_FileWriter only /// maintains information for tags of interest as metadata. /// -/// The needs of XMPFiles are well defined metadata access. Only 5 IFDs are recognized: +/// The needs of XMPFiles are well defined metadata access. Only 4 IFDs are processed: /// \li The 0th IFD, for the primary image, the first one in the outer list of IFDs. -/// \li The 1st IFD, for an Exif thumbnail, the second one in the outer list of IFDs. /// \li The Exif general metadata IFD, from tag 34665 in the primary image IFD. /// \li The Exif GPS Info metadata IFD, from tag 34853 in the primary image IFD. /// \li The Exif Interoperability IFD, from tag 40965 in the Exif general metadata IFD. /// -/// \note In the future we should add support for the non-Exif thumbnails in DNG (TIFF/EP) files. -/// /// \note These classes are for use only when directly compiled and linked. They should not be /// packaged in a DLL by themselves. They do not provide any form of C++ ABI protection. // ================================================================================================= @@ -60,7 +57,7 @@ enum { // Constants for the recognized IFDs. kTIFF_PrimaryIFD = 0, // The primary image IFD, also called the 0th IFD. - kTIFF_TNailIFD = 1, // The thumbnail image IFD also called the 1st IFD. + kTIFF_TNailIFD = 1, // The thumbnail image IFD also called the 1st IFD. (not used) kTIFF_ExifIFD = 2, // The Exif general metadata IFD. kTIFF_GPSInfoIFD = 3, // The Exif GPS Info IFD. kTIFF_InteropIFD = 4, // The Exif Interoperability IFD. @@ -143,7 +140,7 @@ enum { kTIFF_GPSInfoIFDPointer = 34853, kTIFF_DNGVersion = 50706, kTIFF_DNGBackwardVersion = 50707, - + // Additional thumbnail IFD tags. We also care about 256, 257, and 259 in thumbnails. kTIFF_JPEGInterchangeFormat = 513, kTIFF_JPEGInterchangeFormatLength = 514, @@ -218,7 +215,7 @@ enum { kTIFF_DeviceSettingDescription = 41995, kTIFF_SubjectDistanceRange = 41996, kTIFF_ImageUniqueID = 42016, - + kTIFF_MakerNote = 37500, // Gets deleted when rewriting memory-based TIFF. // GPS IFD tags. @@ -254,7 +251,7 @@ enum { kTIFF_GPSAreaInformation = 28, kTIFF_GPSDateStamp = 29, kTIFF_GPSDifferential = 30 - + }; // ------------------------------------------------------------------ @@ -437,7 +434,7 @@ public: static const size_t kEmptyTIFFLength = 8; // Just the header. static const size_t kEmptyIFDLength = 2 + 4; // Entry count and next-IFD offset. static const size_t kIFDEntryLength = 12; - + struct TagInfo { XMP_Uns16 id; XMP_Uns16 type; @@ -448,7 +445,7 @@ public: TagInfo ( XMP_Uns16 _id, XMP_Uns16 _type, XMP_Uns32 _count, const void* _dataPtr, XMP_Uns32 _dataLen ) : id(_id), type(_type), count(_count), dataPtr(_dataPtr), dataLen(_dataLen) {}; }; - + typedef std::map<XMP_Uns16,TagInfo> TagInfoMap; struct Rational { XMP_Uns32 num, denom; }; @@ -458,15 +455,14 @@ public: // The IsXyzEndian methods return the external endianness of the original parsed TIFF stream. // The \c GetTag methods return native endian values, the \c SetTag methods take native values. // The original endianness is preserved in output. - + bool IsBigEndian() const { return this->bigEndian; }; bool IsLittleEndian() const { return (! this->bigEndian); }; bool IsNativeEndian() const { return this->nativeEndian; }; - + // --------------------------------------------------------------------------------------------- - // The TIFF_Manager only keeps explicit knowledge of up to 5 IFDs: + // The TIFF_Manager only keeps explicit knowledge of up to 4 IFDs: // - The primary image IFD, also known as the 0th IFD. This must be present. - // - A possible thumbnail IFD, also known as the 1st IFD, chained from the primary image IFD. // - A possible Exif general metadata IFD, found from tag 34665 in the primary image IFD. // - A possible Exif GPS metadata IFD, found from tag 34853 in the primary image IFD. // - A possible Exif Interoperability IFD, found from tag 40965 in the Exif general metadata IFD. @@ -476,11 +472,10 @@ public: // removed. Parsing will sort the tags into ascending order, AppendTIFF and ComposeTIFF will // preserve the sorted order. These fixes do not cause IsChanged to return true, that only // happens if the client makes explicit changes using SetTag or DeleteTag. - - virtual bool HasThumbnailIFD() const = 0; + virtual bool HasExifIFD() const = 0; virtual bool HasGPSInfoIFD() const = 0; - + // --------------------------------------------------------------------------------------------- // These are the basic methods to get a map of all of the tags in an IFD, to get or set a tag, // or to delete a tag. The dataPtr returned by \c GetTag is consided read-only, the client must @@ -491,17 +486,17 @@ public: // \c SetTag replaces an existing tag regardless of type or count. \c DeleteTag deletes a tag, // it is a no-op if the tag does not exist. \c GetValueOffset returns the offset within the // parsed stream of the tag's value. It returns 0 if the tag was not in the parsed input. - + virtual bool GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const = 0; - + virtual bool GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const = 0; virtual void SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* dataPtr ) = 0; - + virtual void DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) = 0; - + virtual XMP_Uns32 GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const = 0; - + // --------------------------------------------------------------------------------------------- // These methods are for tags whose type can be short or long, depending on the actual value. // \c GetTag_Integer returns false if an existing tag's type is not short, or long, or if the @@ -511,7 +506,7 @@ public: virtual bool GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const = 0; void SetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 data ); - + // --------------------------------------------------------------------------------------------- // These are customized forms of GetTag that verify the type and return a typed value. False is // returned if the type does not match or if the count is not 1. @@ -532,7 +527,7 @@ public: virtual bool GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const = 0; virtual bool GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const = 0; - + // --------------------------------------------------------------------------------------------- void SetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8 data ); @@ -549,18 +544,14 @@ public: void SetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double data ); void SetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr dataPtr ); - + // --------------------------------------------------------------------------------------------- virtual bool GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const = 0; virtual void SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ) = 0; - + bool DecodeString ( const void * encodedPtr, size_t encodedLen, std::string* utf8Str ) const; bool EncodeString ( const std::string& utf8Str, XMP_Uns8 encoding, std::string* encodedStr ); - - // --------------------------------------------------------------------------------------------- - - bool GetTNailInfo ( XMP_ThumbnailInfo * tnailInfo ) const; // --------------------------------------------------------------------------------------------- // \c IsChanged returns true if a read-write stream has changes that need to be saved. This is @@ -596,17 +587,17 @@ public: // The condenseStream parameter to UpdateMemoryStream can be used to rewrite the full stream // instead of appending. This will discard any MakerNote tags and risks breaking offsets that // are hidden. This can be necessary though to try to make the TIFF fit in a JPEG file. - + virtual void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true ) = 0; virtual void ParseFileStream ( LFA_FileRef fileRef ) = 0; - + virtual void IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) = 0; - + virtual XMP_Uns32 UpdateMemoryStream ( void** dataPtr, bool condenseStream = false ) = 0; virtual void UpdateFileStream ( LFA_FileRef fileRef ) = 0; - + // --------------------------------------------------------------------------------------------- - + GetUns16_Proc GetUns16; // Get values from the TIFF stream. GetUns32_Proc GetUns32; // Always native endian on the outside, stream endian in the stream. GetFloat_Proc GetFloat; @@ -618,18 +609,13 @@ public: PutDouble_Proc PutDouble; virtual ~TIFF_Manager() {}; - - // ! Hacks to help the reconciliation code accomodate Photoshop behavior: - bool xmpHadUserComment, xmpHadRelatedSoundFile; protected: bool bigEndian, nativeEndian; - - XMP_Uns8 * jpegTNailPtr; XMP_Uns32 CheckTIFFHeader ( const XMP_Uns8* tiffPtr, XMP_Uns32 length ); - + TIFF_Manager(); // Force clients to use the reader or writer derived classes. struct RawIFDEntry { @@ -653,18 +639,17 @@ protected: class TIFF_MemoryReader : public TIFF_Manager { // The derived class for memory-based read-only access. public: - bool HasThumbnailIFD() const { return (containedIFDs[kTIFF_TNailIFD].count != 0); }; bool HasExifIFD() const { return (containedIFDs[kTIFF_ExifIFD].count != 0); }; bool HasGPSInfoIFD() const { return (containedIFDs[kTIFF_GPSInfoIFD].count != 0); }; bool GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const; - + bool GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const; void SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* dataPtr ) { NotAppropriate(); }; - + void DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) { NotAppropriate(); }; - + XMP_Uns32 GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const; bool GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const; @@ -687,18 +672,18 @@ public: bool GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const; void SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ) { NotAppropriate(); }; - + bool IsChanged() { return false; }; bool IsLegacyChanged() { return false; }; - + void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true ); void ParseFileStream ( LFA_FileRef fileRef ) { NotAppropriate(); }; - + void IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) { NotAppropriate(); }; - + XMP_Uns32 UpdateMemoryStream ( void** dataPtr, bool condenseStream = false ) { if ( dataPtr != 0 ) *dataPtr = tiffStream; return tiffLength; }; void UpdateFileStream ( LFA_FileRef fileRef ) { NotAppropriate(); }; - + TIFF_MemoryReader() : ownedStream(false), tiffStream(0), tiffLength(0) {}; virtual ~TIFF_MemoryReader() { if ( this->ownedStream ) free ( this->tiffStream ); }; @@ -722,26 +707,26 @@ private: XMP_Uns32 dataOrPos; TweakedIFDEntry() : id(0), type(0), bytes(0), dataOrPos(0) {}; }; - + struct TweakedIFDInfo { XMP_Uns16 count; TweakedIFDEntry* entries; TweakedIFDInfo() : count(0), entries(0) {}; }; - + TweakedIFDInfo containedIFDs[kTIFF_KnownIFDCount]; static void SortIFD ( TweakedIFDInfo* thisIFD ); XMP_Uns32 ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ); - + const TweakedIFDEntry* FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const; - + const inline void* GetDataPtr ( const TweakedIFDEntry* tifdEntry ) const { if ( tifdEntry->bytes <= 4 ) return &tifdEntry->dataOrPos; else return (this->tiffStream + tifdEntry->dataOrPos); }; static inline void NotAppropriate() { XMP_Throw ( "Not appropriate for TIFF_Reader", kXMPErr_InternalFailure ); }; - + }; // TIFF_MemoryReader @@ -756,18 +741,17 @@ private: class TIFF_FileWriter : public TIFF_Manager { // The derived class for file-based or read-write access. public: - bool HasThumbnailIFD() const { return this->containedIFDs[kTIFF_TNailIFD].tagMap.size() != 0; }; bool HasExifIFD() const { return this->containedIFDs[kTIFF_ExifIFD].tagMap.size() != 0; }; bool HasGPSInfoIFD() const { return this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.size() != 0; }; bool GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const; - + bool GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const; void SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* dataPtr ); - + void DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ); - + XMP_Uns32 GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const; bool GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const; @@ -790,13 +774,13 @@ public: bool GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const; void SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ); - + bool IsChanged() { return this->changed; }; - + bool IsLegacyChanged(); - + enum { kDoNotCopyData = false }; - + void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true ); void ParseFileStream ( LFA_FileRef fileRef ); @@ -824,13 +808,13 @@ private: // the smallValue field for small values. This is also the usage when a tag is changed (for both // memory and file cases), the dataPtr is a separate allocation for large values (over 4 bytes), // and points to the smallValue field for small values. - + // ! The working data values are always stream endian, no matter where stored. They are flipped // ! as necessary by GetTag and SetTag. - + static const bool kIsFileBased = true; // For use in the InternalTagInfo constructor. static const bool kIsMemoryBased = false; - + class InternalTagInfo { public: @@ -875,9 +859,9 @@ private: origDataLen(0), origDataOffset(0), changed(false), fileBased(false) {}; }; - + typedef std::map<XMP_Uns16,InternalTagInfo> InternalTagMap; - + struct InternalIFDInfo { bool changed; XMP_Uns16 origCount; // Original number of IFD entries. @@ -893,12 +877,12 @@ private: this->tagMap.clear(); }; }; - + InternalIFDInfo containedIFDs[kTIFF_KnownIFDCount]; - + static XMP_Uns8 PickIFD ( XMP_Uns8 ifd, XMP_Uns16 id ); const InternalTagInfo* FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const; - + void DeleteExistingInfo(); XMP_Uns32 ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ); @@ -908,10 +892,8 @@ private: void* CopyTagToMasterIFD ( const TagInfo& ps6Tag, InternalIFDInfo* masterIFD ); - void UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out, - bool appendAll = false, XMP_Uns32 extraSpace = 0 ); - void UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out ); - + void PreflightIFDLinkage(); + XMP_Uns32 DetermineVisibleLength(); XMP_Uns32 DetermineAppendInfo ( XMP_Uns32 appendedOrigin, @@ -919,8 +901,12 @@ private: XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount], bool appendAll = false ); + void UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out, + bool appendAll = false, XMP_Uns32 extraSpace = 0 ); + void UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out ); + void WriteFileIFD ( LFA_FileRef fileRef, InternalIFDInfo & thisIFD ); - + }; // TIFF_FileWriter diff --git a/source/XMPFiles/FormatSupport/XDCAM_Support.cpp b/source/XMPFiles/FormatSupport/XDCAM_Support.cpp index ac0fe1e..b86e69e 100644 --- a/source/XMPFiles/FormatSupport/XDCAM_Support.cpp +++ b/source/XMPFiles/FormatSupport/XDCAM_Support.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2008 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -94,7 +94,7 @@ bool GetLegacyMetaData ( SXMPMeta * xmpObjPtr, } // Modify Date - if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "LastUpdate" )) ) { + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "ModifyDate" )) ) { legacyProp = rootElem->GetNamedElement ( legacyNS, "LastUpdate" ); if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) { XMP_StringPtr legacyValue = legacyProp->GetAttrValue ( "value" ); @@ -106,7 +106,7 @@ bool GetLegacyMetaData ( SXMPMeta * xmpObjPtr, } // Metadata Modify Date - if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "lastUpdate" )) ) { + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_XMP, "MetadataDate" )) ) { legacyProp = rootElem->GetNamedElement ( legacyNS, "lastUpdate" ); if ( (legacyProp != 0) && legacyProp->IsEmptyLeafNode() ) { XMP_StringPtr legacyValue = legacyProp->GetAttrValue ( "value" ); @@ -117,6 +117,18 @@ bool GetLegacyMetaData ( SXMPMeta * xmpObjPtr, } } + // Description + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DC, "description" )) ) { + legacyProp = rootElem->GetNamedElement ( legacyNS, "Description" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + XMP_StringPtr legacyValue = legacyProp->GetLeafContentValue(); + if ( legacyValue != 0 ) { + xmpObjPtr->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", legacyValue, kXMP_DeleteExisting ); + containsXMP = true; + } + } + } + legacyContext = rootElem->GetNamedElement ( legacyNS, "VideoFormat" ); if ( legacyContext != 0 ) { @@ -228,6 +240,54 @@ bool GetLegacyMetaData ( SXMPMeta * xmpObjPtr, } + legacyContext = rootElem->GetNamedElement ( legacyNS, "Device" ); + if ( legacyContext != 0 ) { + + std::string model; + + // manufacturer string + XMP_StringPtr manufacturer = legacyContext->GetAttrValue ( "manufacturer" ); + if ( manufacturer != 0 ) { + model += manufacturer; + } + + // model string + XMP_StringPtr modelName = legacyContext->GetAttrValue ( "modelName" ); + if ( modelName != 0 ) { + if ( model.size() > 0 ) { + model += " "; + } + model += modelName; + } + + + // For the dm::cameraModel property, concat the make and model. + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_DM, "cameraModel" )) ) { + if ( model.size() != 0 ) { + xmpObjPtr->SetProperty ( kXMP_NS_DM, "cameraModel", model, kXMP_DeleteExisting ); + containsXMP = true; + } + } + + // EXIF Model + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_TIFF, "Model" )) ) { + xmpObjPtr->SetProperty ( kXMP_NS_TIFF, "Model", modelName, kXMP_DeleteExisting ); + } + + // EXIF Make + if ( digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_TIFF, "Make" )) ) { + xmpObjPtr->SetProperty ( kXMP_NS_TIFF, "Make", manufacturer, kXMP_DeleteExisting ); + } + + // EXIF-AUX Serial number + XMP_StringPtr serialNumber = legacyContext->GetAttrValue ( "serialNo" ); + if ( serialNumber != 0 && (digestFound || (! xmpObjPtr->DoesPropertyExist ( kXMP_NS_EXIF_Aux, "SerialNumber" ))) ) { + xmpObjPtr->SetProperty ( kXMP_NS_EXIF_Aux, "SerialNumber", serialNumber, kXMP_DeleteExisting ); + } + + } + + return containsXMP; } diff --git a/source/XMPFiles/FormatSupport/XDCAM_Support.hpp b/source/XMPFiles/FormatSupport/XDCAM_Support.hpp index 3fe1e65..51811c7 100644 --- a/source/XMPFiles/FormatSupport/XDCAM_Support.hpp +++ b/source/XMPFiles/FormatSupport/XDCAM_Support.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2006-2007 Adobe Systems Incorporated +// Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/XMPScanner.cpp b/source/XMPFiles/FormatSupport/XMPScanner.cpp index 4396a25..a67c2c6 100644 --- a/source/XMPFiles/FormatSupport/XMPScanner.cpp +++ b/source/XMPFiles/FormatSupport/XMPScanner.cpp @@ -1,5 +1,5 @@ // ================================================================================================= -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/FormatSupport/XMPScanner.hpp b/source/XMPFiles/FormatSupport/XMPScanner.hpp index 2c8b6fa..472a43e 100644 --- a/source/XMPFiles/FormatSupport/XMPScanner.hpp +++ b/source/XMPFiles/FormatSupport/XMPScanner.hpp @@ -2,7 +2,7 @@ #define __XMPScanner_hpp__ // ================================================================================================= -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms diff --git a/source/XMPFiles/WXMPFiles.cpp b/source/XMPFiles/WXMPFiles.cpp index 5976b48..4e32607 100644 --- a/source/XMPFiles/WXMPFiles.cpp +++ b/source/XMPFiles/WXMPFiles.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -34,33 +34,23 @@ static WXMP_Result voidResult; // Used for functions that don't use the normal void WXMPFiles_GetVersionInfo_1 ( XMP_VersionInfo * versionInfo ) { WXMP_Result * wResult = &voidResult; // ! Needed to "fool" the EnterWrapper macro. - XMP_ENTER_WRAPPER_NO_LOCK ( "WXMPFiles_GetVersionInfo_1" ) + XMP_ENTER_NoLock ( "WXMPFiles_GetVersionInfo_1" ) XMPFiles::GetVersionInfo ( versionInfo ); - XMP_EXIT_WRAPPER_NO_THROW + XMP_EXIT_NoThrow } // ------------------------------------------------------------------------------------------------- -void WXMPFiles_Initialize_1 ( WXMP_Result * wResult ) +void WXMPFiles_Initialize_1 ( XMP_OptionBits options, + WXMP_Result * wResult ) { - XMP_ENTER_WRAPPER_NO_LOCK ( "WXMPFiles_Initialize_1" ) - - wResult->int32Result = XMPFiles::Initialize ( 0 ); - - XMP_EXIT_WRAPPER -} - -// ------------------------------------------------------------------------------------------------- - -void WXMPFiles_Initialize_2 ( XMP_OptionBits options, WXMP_Result * wResult ) -{ - XMP_ENTER_WRAPPER_NO_LOCK ( "WXMPFiles_Initialize_1" ) + XMP_ENTER_NoLock ( "WXMPFiles_Initialize_1" ) wResult->int32Result = XMPFiles::Initialize ( options ); - XMP_EXIT_WRAPPER + XMP_EXIT } // ------------------------------------------------------------------------------------------------- @@ -68,79 +58,55 @@ void WXMPFiles_Initialize_2 ( XMP_OptionBits options, WXMP_Result * wResult ) void WXMPFiles_Terminate_1() { WXMP_Result * wResult = &voidResult; // ! Needed to "fool" the EnterWrapper macro. - XMP_ENTER_WRAPPER_NO_LOCK ( "WXMPFiles_Terminate_1" ) + XMP_ENTER_NoLock ( "WXMPFiles_Terminate_1" ) XMPFiles::Terminate(); - XMP_EXIT_WRAPPER_NO_THROW + XMP_EXIT_NoThrow } // ================================================================================================= void WXMPFiles_CTor_1 ( WXMP_Result * wResult ) { - XMP_ENTER_WRAPPER ( "WXMPFiles_CTor_1" ) + XMP_ENTER_Static ( "WXMPFiles_CTor_1" ) // No lib object yet, use the static entry. XMPFiles * newObj = new XMPFiles(); ++newObj->clientRefs; XMP_Assert ( newObj->clientRefs == 1 ); wResult->ptrResult = newObj; - XMP_EXIT_WRAPPER -} - -// ------------------------------------------------------------------------------------------------- - -void WXMPFiles_UnlockLib_1() -{ - WXMP_Result * wResult = &voidResult; // ! Needed to fool the EnterWrapper macro. - XMP_ENTER_WRAPPER_NO_LOCK ( "WXMPFiles_UnlockLib_1" ) - - XMPFiles::UnlockLib(); - - XMP_EXIT_WRAPPER_NO_THROW + XMP_EXIT } // ------------------------------------------------------------------------------------------------- -void WXMPFiles_UnlockObj_1 ( XMPFilesRef xmpFilesRef ) -{ - WXMP_Result * wResult = &voidResult; // ! Needed to fool the EnterWrapper macro. - XMP_ENTER_WRAPPER_NO_LOCK ( "WXMPFiles_UnlockObj_1" ) - - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; - thiz->UnlockObj(); - - XMP_EXIT_WRAPPER_NO_THROW -} - -// ------------------------------------------------------------------------------------------------- - -void WXMPFiles_IncrementRefCount_1 ( XMPFilesRef xmpFilesRef ) +void WXMPFiles_IncrementRefCount_1 ( XMPFilesRef xmpObjRef ) { WXMP_Result * wResult = &voidResult; // ! Needed to "fool" the EnterWrapper macro. - XMP_ENTER_WRAPPER ( "WXMPFiles_IncrementRefCount_1" ) + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_IncrementRefCount_1" ) - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; ++thiz->clientRefs; XMP_Assert ( thiz->clientRefs > 0 ); - XMP_EXIT_WRAPPER_NO_THROW + XMP_EXIT_NoThrow } // ------------------------------------------------------------------------------------------------- -void WXMPFiles_DecrementRefCount_1 ( XMPFilesRef xmpFilesRef ) +void WXMPFiles_DecrementRefCount_1 ( XMPFilesRef xmpObjRef ) { WXMP_Result * wResult = &voidResult; // ! Needed to "fool" the EnterWrapper macro. - XMP_ENTER_WRAPPER ( "WXMPFiles_DecrementRefCount_1" ) + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_DecrementRefCount_1" ) - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; XMP_Assert ( thiz->clientRefs > 0 ); --thiz->clientRefs; - if ( thiz->clientRefs <= 0 ) delete ( thiz ); + if ( thiz->clientRefs <= 0 ) { + objLock.Release(); + delete ( thiz ); + } - XMP_EXIT_WRAPPER_NO_THROW + XMP_EXIT_NoThrow } // ================================================================================================= @@ -149,11 +115,11 @@ void WXMPFiles_GetFormatInfo_1 ( XMP_FileFormat format, XMP_OptionBits * flags, WXMP_Result * wResult ) { - XMP_ENTER_WRAPPER ( "WXMPFiles_GetFormatInfo_1" ) + XMP_ENTER_Static ( "WXMPFiles_GetFormatInfo_1" ) wResult->int32Result = XMPFiles::GetFormatInfo ( format, flags ); - XMP_EXIT_WRAPPER + XMP_EXIT } // ================================================================================================= @@ -161,11 +127,11 @@ void WXMPFiles_GetFormatInfo_1 ( XMP_FileFormat format, void WXMPFiles_CheckFileFormat_1 ( XMP_StringPtr filePath, WXMP_Result * wResult ) { - XMP_ENTER_WRAPPER ( "WXMPFiles_CheckFileFormat_1" ) + XMP_ENTER_Static ( "WXMPFiles_CheckFileFormat_1" ) wResult->int32Result = XMPFiles::CheckFileFormat ( filePath ); - XMP_EXIT_WRAPPER + XMP_EXIT } // ================================================================================================= @@ -173,138 +139,123 @@ void WXMPFiles_CheckFileFormat_1 ( XMP_StringPtr filePath, void WXMPFiles_CheckPackageFormat_1 ( XMP_StringPtr folderPath, WXMP_Result * wResult ) { - XMP_ENTER_WRAPPER ( "WXMPFiles_CheckPackageFormat_1" ) + XMP_ENTER_Static ( "WXMPFiles_CheckPackageFormat_1" ) wResult->int32Result = XMPFiles::CheckPackageFormat ( folderPath ); - XMP_EXIT_WRAPPER + XMP_EXIT } // ================================================================================================= -void WXMPFiles_OpenFile_1 ( XMPFilesRef xmpFilesRef, +void WXMPFiles_OpenFile_1 ( XMPFilesRef xmpObjRef, XMP_StringPtr filePath, XMP_FileFormat format, XMP_OptionBits openFlags, WXMP_Result * wResult ) { - XMP_ENTER_WRAPPER ( "WXMPFiles_OpenFile_1" ) + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_OpenFile_1" ) StartPerfCheck ( kAPIPerf_OpenFile, filePath ); - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; bool ok = thiz->OpenFile ( filePath, format, openFlags ); wResult->int32Result = ok; EndPerfCheck ( kAPIPerf_OpenFile ); - XMP_EXIT_WRAPPER + XMP_EXIT } // ------------------------------------------------------------------------------------------------- -void WXMPFiles_CloseFile_1 ( XMPFilesRef xmpFilesRef, +void WXMPFiles_CloseFile_1 ( XMPFilesRef xmpObjRef, XMP_OptionBits closeFlags, WXMP_Result * wResult ) { - XMP_ENTER_WRAPPER ( "WXMPFiles_CloseFile_1" ) + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_CloseFile_1" ) StartPerfCheck ( kAPIPerf_CloseFile, "" ); - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; thiz->CloseFile ( closeFlags ); EndPerfCheck ( kAPIPerf_CloseFile ); - XMP_EXIT_WRAPPER + XMP_EXIT } // ------------------------------------------------------------------------------------------------- -void WXMPFiles_GetFileInfo_1 ( XMPFilesRef xmpFilesRef, - XMP_StringPtr * filePath, - XMP_StringLen * filePathLen, +void WXMPFiles_GetFileInfo_1 ( XMPFilesRef xmpObjRef, + void * clientPath, XMP_OptionBits * openFlags, XMP_FileFormat * format, XMP_OptionBits * handlerFlags, + SetClientStringProc SetClientString, WXMP_Result * wResult ) { - bool isOpen = false; - XMP_ENTER_WRAPPER ( "WXMPFiles_GetFileInfo_1" ) + XMP_ENTER_ObjRead ( XMPFiles, "WXMPFiles_GetFileInfo_1" ) - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; - isOpen = thiz->GetFileInfo ( filePath, filePathLen, openFlags, format, handlerFlags ); + XMP_StringPtr pathStr; + XMP_StringLen pathLen; + + bool isOpen = thiz.GetFileInfo ( &pathStr, &pathLen, openFlags, format, handlerFlags ); + if ( isOpen && (clientPath != 0) ) (*SetClientString) ( clientPath, pathStr, pathLen ); wResult->int32Result = isOpen; - XMP_EXIT_WRAPPER_KEEP_LOCK ( isOpen ) + XMP_EXIT } // ------------------------------------------------------------------------------------------------- -void WXMPFiles_SetAbortProc_1 ( XMPFilesRef xmpFilesRef, +void WXMPFiles_SetAbortProc_1 ( XMPFilesRef xmpObjRef, XMP_AbortProc abortProc, void * abortArg, WXMP_Result * wResult ) { - XMP_ENTER_WRAPPER ( "WXMPFiles_SetAbortProc_1" ) + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_SetAbortProc_1" ) - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; thiz->SetAbortProc ( abortProc, abortArg ); - XMP_EXIT_WRAPPER + XMP_EXIT } // ------------------------------------------------------------------------------------------------- -void WXMPFiles_GetXMP_1 ( XMPFilesRef xmpFilesRef, +void WXMPFiles_GetXMP_1 ( XMPFilesRef xmpObjRef, XMPMetaRef xmpRef, - XMP_StringPtr * xmpPacket, - XMP_StringLen * xmpPacketLen, + void * clientPacket, XMP_PacketInfo * packetInfo, + SetClientStringProc SetClientString, WXMP_Result * wResult ) { - bool hasXMP = false; - XMP_ENTER_WRAPPER ( "WXMPFiles_GetXMP_1" ) + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_GetXMP_1" ) StartPerfCheck ( kAPIPerf_GetXMP, "" ); - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; + bool hasXMP = false; + XMP_StringPtr packetStr; + XMP_StringLen packetLen; + if ( xmpRef == 0 ) { - hasXMP = thiz->GetXMP ( 0, xmpPacket, xmpPacketLen, packetInfo ); + hasXMP = thiz->GetXMP ( 0, &packetStr, &packetLen, packetInfo ); } else { SXMPMeta xmpObj ( xmpRef ); - hasXMP = thiz->GetXMP ( &xmpObj, xmpPacket, xmpPacketLen, packetInfo ); + hasXMP = thiz->GetXMP ( &xmpObj, &packetStr, &packetLen, packetInfo ); } + + if ( hasXMP && (clientPacket != 0) ) (*SetClientString) ( clientPacket, packetStr, packetLen ); wResult->int32Result = hasXMP; EndPerfCheck ( kAPIPerf_GetXMP ); - XMP_EXIT_WRAPPER_KEEP_LOCK ( hasXMP ) -} - -// ------------------------------------------------------------------------------------------------- - -void WXMPFiles_GetThumbnail_1 ( XMPFilesRef xmpFilesRef, - XMP_ThumbnailInfo * tnailInfo, // ! Can be null. - WXMP_Result * wResult ) -{ - XMP_ENTER_WRAPPER ( "WXMPFiles_GetThumbnail_1" ) - StartPerfCheck ( kAPIPerf_GetThumbnail, "" ); - - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; - bool hasTNail = thiz->GetThumbnail ( tnailInfo ); - wResult->int32Result = hasTNail; - - EndPerfCheck ( kAPIPerf_GetThumbnail ); - XMP_EXIT_WRAPPER // ! No need to keep the lock, the tnail info won't change. + XMP_EXIT } // ------------------------------------------------------------------------------------------------- -void WXMPFiles_PutXMP_1 ( XMPFilesRef xmpFilesRef, +void WXMPFiles_PutXMP_1 ( XMPFilesRef xmpObjRef, XMPMetaRef xmpRef, // ! Only one of the XMP object or packet are passed. XMP_StringPtr xmpPacket, XMP_StringLen xmpPacketLen, WXMP_Result * wResult ) { - XMP_ENTER_WRAPPER ( "WXMPFiles_PutXMP_1" ) + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_PutXMP_1" ) StartPerfCheck ( kAPIPerf_PutXMP, "" ); - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; if ( xmpRef != 0 ) { thiz->PutXMP ( xmpRef ); } else { @@ -312,21 +263,20 @@ void WXMPFiles_PutXMP_1 ( XMPFilesRef xmpFilesRef, } EndPerfCheck ( kAPIPerf_PutXMP ); - XMP_EXIT_WRAPPER + XMP_EXIT } // ------------------------------------------------------------------------------------------------- -void WXMPFiles_CanPutXMP_1 ( XMPFilesRef xmpFilesRef, +void WXMPFiles_CanPutXMP_1 ( XMPFilesRef xmpObjRef, XMPMetaRef xmpRef, // ! Only one of the XMP object or packet are passed. XMP_StringPtr xmpPacket, XMP_StringLen xmpPacketLen, WXMP_Result * wResult ) { - XMP_ENTER_WRAPPER ( "WXMPFiles_CanPutXMP_1" ) + XMP_ENTER_ObjWrite ( XMPFiles, "WXMPFiles_CanPutXMP_1" ) StartPerfCheck ( kAPIPerf_CanPutXMP, "" ); - XMPFiles * thiz = (XMPFiles*)xmpFilesRef; if ( xmpRef != 0 ) { wResult->int32Result = thiz->CanPutXMP ( xmpRef ); } else { @@ -334,7 +284,7 @@ void WXMPFiles_CanPutXMP_1 ( XMPFilesRef xmpFilesRef, } EndPerfCheck ( kAPIPerf_CanPutXMP ); - XMP_EXIT_WRAPPER + XMP_EXIT } // ================================================================================================= diff --git a/source/XMPFiles/XMPFiles.cpp b/source/XMPFiles/XMPFiles.cpp index 8471675..ba16e3f 100644 --- a/source/XMPFiles/XMPFiles.cpp +++ b/source/XMPFiles/XMPFiles.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2008 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -22,8 +22,7 @@ #include "FileHandlers/Scanner_Handler.hpp" #include "FileHandlers/MPEG2_Handler.hpp" #include "FileHandlers/PNG_Handler.hpp" -#include "FileHandlers/AVI_Handler.hpp" -#include "FileHandlers/WAV_Handler.hpp" +#include "FileHandlers/RIFF_Handler.hpp" #include "FileHandlers/MP3_Handler.hpp" #include "FileHandlers/SWF_Handler.hpp" #include "FileHandlers/UCF_Handler.hpp" @@ -36,11 +35,6 @@ #include "FileHandlers/AVCHD_Handler.hpp" #include "FileHandlers/ASF_Handler.hpp" -#if ! (XMP_64 || XMP_UNIXBuild) - #include "QuickTime_Support.hpp" - #include "FileHandlers/MOV_Handler.hpp" //old MOV handler -#endif - // ================================================================================================= /// \file XMPFiles.cpp /// \brief High level support to access metadata in files of interest to Adobe applications. @@ -51,7 +45,7 @@ // ================================================================================================= -long sXMPFilesInitCount = 0; +XMP_Int32 sXMPFilesInitCount = 0; #if GatherPerformanceData APIPerfCollection* sAPIPerf = 0; @@ -236,7 +230,7 @@ CheckParentFolderNames ( const std::string & rootPath, const std::string & gpN // P2 .../MyMovie/CONTENTS/<group>/<file>.<ext> - check CONTENTS and <group> if ( (gpName == "CONTENTS") && CheckP2ContentChild ( parentName ) ) return kXMP_P2File; - // XDCAMEX .../MyMovie/BPAV/CLPR/<clip>/<file>.<ext> - check for BPAV/CLPR + // XDCAM-EX .../MyMovie/BPAV/CLPR/<clip>/<file>.<ext> - check for BPAV/CLPR // ! This must be checked before XDCAM-SAM because both have a "CLPR" grandparent. if ( gpName == "CLPR" ) { std::string tempPath, greatGP; @@ -247,6 +241,7 @@ CheckParentFolderNames ( const std::string & rootPath, const std::string & gpN } // XDCAM-FAM .../MyMovie/<group>/<file>.<ext> - check that <group> is CLIP, or EDIT, or SUB + // ! The standard says Clip/Edit/Sub, but the caller has already shifted to upper case. if ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) return kXMP_XDCAM_FAMFile; // XDCAM-SAM .../MyMovie/PROAV/<group>/<clip>/<file>.<ext> - check for PROAV and CLPR or EDTR @@ -292,8 +287,8 @@ CheckTopFolderName ( const std::string & rootPath ) if ( GetFileMode ( childPath.c_str() ) == kFMode_IsFolder ) return kXMP_P2File; childPath.erase ( baseLen ); - // XDCAM-FAM .../MyMovie/<group>/... - only check for CLIP and MEDIAPRO.XML - childPath += "CLIP"; + // XDCAM-FAM .../MyMovie/<group>/... - only check for Clip and MEDIAPRO.XML + childPath += "Clip"; // ! Yes, mixed case. if ( GetFileMode ( childPath.c_str() ) == kFMode_IsFolder ) { childPath.erase ( baseLen ); childPath += "MEDIAPRO.XML"; @@ -340,15 +335,12 @@ TryFolderHandlers ( XMP_FileFormat format, const std::string & rootPath, const std::string & gpName, const std::string & parentName, - const std::string & _leafName, + const std::string & leafName, XMPFiles * parentObj ) { bool foundHandler = false; XMPFileHandlerInfo * handlerInfo = 0; XMPFileHandlerTablePos handlerPos; - - std::string leafName ( _leafName ); - MakeUpperCase ( &leafName ); // We know we're in a possible context for a folder-oriented handler, so try them. @@ -360,7 +352,7 @@ TryFolderHandlers ( XMP_FileFormat format, handlerInfo = &handlerPos->second; CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc); foundHandler = CheckProc ( handlerInfo->format, rootPath, gpName, parentName, leafName, parentObj ); - XMP_Assert ( foundHandler || (parentObj->handlerTemp == 0) ); + XMP_Assert ( foundHandler || (parentObj->tempPtr == 0) ); } } else { @@ -370,7 +362,7 @@ TryFolderHandlers ( XMP_FileFormat format, handlerInfo = &handlerPos->second; CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc); foundHandler = CheckProc ( handlerInfo->format, rootPath, gpName, parentName, leafName, parentObj ); - XMP_Assert ( foundHandler || (parentObj->handlerTemp == 0) ); + XMP_Assert ( foundHandler || (parentObj->tempPtr == 0) ); if ( foundHandler ) break; // ! Exit before incrementing handlerPos. } @@ -501,11 +493,11 @@ SelectSmartHandler ( XMPFiles * thiz, XMP_StringPtr clientPath, XMP_FileFormat f thiz->fileRef = LFA_Open ( clientPath, openMode ); XMP_Assert ( thiz->fileRef != 0 ); // LFA_Open must either succeed or throw. } - thiz->format = handlerInfo->format; // ! Hack to tell the CheckProc thiz is an initial call. + thiz->format = format; // ! Hack to tell the CheckProc thiz is an initial call. if ( ! (handlerInfo->flags & kXMPFiles_FolderBasedFormat) ) { CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc); - foundHandler = CheckProc ( handlerInfo->format, clientPath, thiz->fileRef, thiz ); + foundHandler = CheckProc ( format, clientPath, thiz->fileRef, thiz ); } else { // *** Don't try here yet. These are messy, needing existence checking and path processing. // *** CheckFolderFormatProc CheckProc = (CheckFolderFormatProc) (handlerInfo->checkProc); @@ -514,7 +506,7 @@ SelectSmartHandler ( XMPFiles * thiz, XMP_StringPtr clientPath, XMP_FileFormat f if ( openFlags & kXMPFiles_OpenStrictly ) openFlags ^= kXMPFiles_OpenStrictly; } - XMP_Assert ( foundHandler || (thiz->handlerTemp == 0) ); + XMP_Assert ( foundHandler || (thiz->tempPtr == 0) ); if ( foundHandler ) return handlerInfo; handlerInfo = 0; // ! Clear again for later use. @@ -539,7 +531,7 @@ SelectSmartHandler ( XMPFiles * thiz, XMP_StringPtr clientPath, XMP_FileFormat f // 3c. Make sure the root folder has a viable top level child folder. // ! This does "return 0" on failure, the file does not exist so a normal file handler can't apply. - + if ( GetFileMode ( rootPath.c_str() ) != kFMode_IsFolder ) return 0; thiz->format = CheckTopFolderName ( rootPath ); if ( thiz->format == kXMP_UnknownFile ) return 0; @@ -569,6 +561,7 @@ SelectSmartHandler ( XMPFiles * thiz, XMP_StringPtr clientPath, XMP_FileFormat f if ( (thiz->format == kXMP_XDCAM_FAMFile) && ((parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB")) ) { + // ! The standard says Clip/Edit/Sub, but we just shifted to upper case. gpName = origGPName; // ! XDCAM-FAM has just 1 level of inner folder, preserve the "MyMovie" case. } @@ -585,11 +578,14 @@ SelectSmartHandler ( XMPFiles * thiz, XMP_StringPtr clientPath, XMP_FileFormat f if ( (thiz->fileRef == 0) && (! (handlerInfo->flags & kXMPFiles_HandlerOwnsFile)) ) { thiz->fileRef = LFA_Open ( clientPath, openMode ); XMP_Assert ( thiz->fileRef != 0 ); // LFA_Open must either succeed or throw. + } else if ( (thiz->fileRef != 0) && (handlerInfo->flags & kXMPFiles_HandlerOwnsFile) ) { + LFA_Close ( thiz->fileRef ); + thiz->fileRef = 0; } thiz->format = handlerInfo->format; // ! Hack to tell the CheckProc thiz is an initial call. CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc); foundHandler = CheckProc ( handlerInfo->format, clientPath, thiz->fileRef, thiz ); - XMP_Assert ( foundHandler || (thiz->handlerTemp == 0) ); + XMP_Assert ( foundHandler || (thiz->tempPtr == 0) ); if ( foundHandler ) return handlerInfo; } @@ -604,7 +600,7 @@ SelectSmartHandler ( XMPFiles * thiz, XMP_StringPtr clientPath, XMP_FileFormat f handlerInfo = &handlerPos->second; CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc); foundHandler = CheckProc ( handlerInfo->format, clientPath, thiz->fileRef, thiz ); - XMP_Assert ( foundHandler || (thiz->handlerTemp == 0) ); + XMP_Assert ( foundHandler || (thiz->tempPtr == 0) ); if ( foundHandler ) return handlerInfo; } @@ -619,7 +615,7 @@ SelectSmartHandler ( XMPFiles * thiz, XMP_StringPtr clientPath, XMP_FileFormat f handlerInfo = &handlerPos->second; CheckFileFormatProc CheckProc = (CheckFileFormatProc) (handlerInfo->checkProc); foundHandler = CheckProc ( handlerInfo->format, clientPath, thiz->fileRef, thiz ); - XMP_Assert ( foundHandler || (thiz->handlerTemp == 0) ); + XMP_Assert ( foundHandler || (thiz->tempPtr == 0) ); if ( foundHandler ) return handlerInfo; } @@ -649,8 +645,12 @@ XMPFiles::GetVersionInfo ( XMP_VersionInfo * info ) // ================================================================================================= -#if ! (XMP_64 || XMP_UNIXBuild) - static bool sIgnoreQuickTime = false; // Not vital, but helps catching missing excludes elsewhere. +#if XMP_TraceFilesCalls + FILE * xmpFilesLog = stderr; +#endif + +#if UseGlobalLibraryLock & (! XMP_StaticBuild ) + XMP_BasicMutex sLibraryLock; // ! Handled in XMPMeta for static builds. #endif /* class static */ @@ -660,13 +660,22 @@ XMPFiles::Initialize ( XMP_OptionBits options /* = 0 */ ) ++sXMPFilesInitCount; if ( sXMPFilesInitCount > 1 ) return true; + #if XMP_TraceFilesCallsToFile + xmpFilesLog = fopen ( "XMPFilesLog.txt", "w" ); + if ( xmpFilesLog == 0 ) xmpFilesLog = stderr; + #endif + + #if UseGlobalLibraryLock & (! XMP_StaticBuild ) + InitializeBasicMutex ( sLibraryLock ); // ! Handled in XMPMeta for static builds. + #endif + SXMPMeta::Initialize(); // Just in case the client does not. + if ( ! Initialize_LibUtils() ) return false; + #if GatherPerformanceData sAPIPerf = new APIPerfCollection; #endif - - XMP_InitMutex ( &sXMPFilesLock ); XMP_Uns16 endianInt = 0x00FF; XMP_Uns8 endianByte = *((XMP_Uns8*)&endianInt); @@ -683,22 +692,13 @@ XMPFiles::Initialize ( XMP_OptionBits options /* = 0 */ ) sNormalHandlers = new XMPFileHandlerTable; sOwningHandlers = new XMPFileHandlerTable; - sXMPFilesExceptionMessage = new XMP_VarString; - InitializeUnicodeConversions(); - #if ! (XMP_64 || XMP_UNIXBuild) - sIgnoreQuickTime = XMP_OptionIsSet ( options, kXMPFiles_NoQuickTimeInit ); - (void) QuickTime_Support::MainInitialize ( sIgnoreQuickTime ); // Don't worry about failure, the MOV handler checks that. + ignoreLocalText = XMP_OptionIsSet ( options, kXMPFiles_IgnoreLocalText ); + #if XMP_UNIXBuild + if ( ! ignoreLocalText ) XMP_Throw ( "Generic UNIX clients must pass kXMPFiles_IgnoreLocalText", kXMPErr_EnforceFailure ); #endif -#if XMP_UNIXBuild - - // *** For the time being only allow the JPEG smart handler for generic UNIX, not even packet scanning. - RegisterNormalHandler ( kXMP_JPEGFile, kJPEG_HandlerFlags, JPEG_CheckFormat, JPEG_MetaHandlerCTor ); - -#else - // ----------------------------------------- // Register the directory-oriented handlers. @@ -723,11 +723,13 @@ XMPFiles::Initialize ( XMP_OptionBits options /* = 0 */ ) RegisterNormalHandler ( kXMP_PostScriptFile, kPostScript_HandlerFlags, PostScript_CheckFormat, PostScript_MetaHandlerCTor ); RegisterNormalHandler ( kXMP_WMAVFile, kASF_HandlerFlags, ASF_CheckFormat, ASF_MetaHandlerCTor ); RegisterNormalHandler ( kXMP_MP3File, kMP3_HandlerFlags, MP3_CheckFormat, MP3_MetaHandlerCTor ); - RegisterNormalHandler ( kXMP_WAVFile, kWAV_HandlerFlags, WAV_CheckFormat, WAV_MetaHandlerCTor ); - RegisterNormalHandler ( kXMP_AVIFile, kAVI_HandlerFlags, AVI_CheckFormat, AVI_MetaHandlerCTor ); + RegisterNormalHandler ( kXMP_WAVFile, kRIFF_HandlerFlags, RIFF_CheckFormat, RIFF_MetaHandlerCTor ); + RegisterNormalHandler ( kXMP_AVIFile, kRIFF_HandlerFlags, RIFF_CheckFormat, RIFF_MetaHandlerCTor ); + RegisterNormalHandler ( kXMP_SWFFile, kSWF_HandlerFlags, SWF_CheckFormat, SWF_MetaHandlerCTor ); RegisterNormalHandler ( kXMP_UCFFile, kUCF_HandlerFlags, UCF_CheckFormat, UCF_MetaHandlerCTor ); RegisterNormalHandler ( kXMP_MPEG4File, kMPEG4_HandlerFlags, MPEG4_CheckFormat, MPEG4_MetaHandlerCTor ); + RegisterNormalHandler ( kXMP_MOVFile, kMPEG4_HandlerFlags, MPEG4_CheckFormat, MPEG4_MetaHandlerCTor ); // ! Yes, MPEG-4 includes MOV. RegisterNormalHandler ( kXMP_FLVFile, kFLV_HandlerFlags, FLV_CheckFormat, FLV_MetaHandlerCTor ); // --------------------------------------------------------------------------------------- @@ -736,14 +738,18 @@ XMPFiles::Initialize ( XMP_OptionBits options /* = 0 */ ) RegisterOwningHandler ( kXMP_MPEGFile, kMPEG2_HandlerFlags, MPEG2_CheckFormat, MPEG2_MetaHandlerCTor ); RegisterOwningHandler ( kXMP_MPEG2File, kMPEG2_HandlerFlags, MPEG2_CheckFormat, MPEG2_MetaHandlerCTor ); - #if ! (XMP_64 || XMP_UNIXBuild) - RegisterOwningHandler ( kXMP_MOVFile, kMOV_HandlerFlags, MOV_CheckFormat, MOV_MetaHandlerCTor ); - #endif - -#endif // XMP_UNIXBuild, temporary exclusions - // Make sure the embedded info strings are referenced and kept. if ( (kXMPFiles_EmbeddedVersion[0] == 0) || (kXMPFiles_EmbeddedCopyright[0] == 0) ) return false; + // Verify critical type sizes. + XMP_Assert ( sizeof(XMP_Int8) == 1 ); + XMP_Assert ( sizeof(XMP_Int16) == 2 ); + XMP_Assert ( sizeof(XMP_Int32) == 4 ); + XMP_Assert ( sizeof(XMP_Int64) == 8 ); + XMP_Assert ( sizeof(XMP_Uns8) == 1 ); + XMP_Assert ( sizeof(XMP_Uns16) == 2 ); + XMP_Assert ( sizeof(XMP_Uns32) == 4 ); + XMP_Assert ( sizeof(XMP_Uns64) == 8 ); + return true; } // XMPFiles::Initialize @@ -825,18 +831,12 @@ static void ReportPerformanceData() // ================================================================================================= -#define EliminateGlobal(g) delete ( g ); g = 0 - /* class static */ void XMPFiles::Terminate() { --sXMPFilesInitCount; - if ( sXMPFilesInitCount != 0 ) return; - - #if ! (XMP_64 || XMP_UNIXBuild) - QuickTime_Support::MainTerminate ( sIgnoreQuickTime ); - #endif + if ( sXMPFilesInitCount != 0 ) return; // Not ready to terminate, or already terminated. #if GatherPerformanceData ReportPerformanceData(); @@ -846,13 +846,20 @@ XMPFiles::Terminate() EliminateGlobal ( sFolderHandlers ); EliminateGlobal ( sNormalHandlers ); EliminateGlobal ( sOwningHandlers ); - - EliminateGlobal ( sXMPFilesExceptionMessage ); - - XMP_TermMutex ( sXMPFilesLock ); SXMPMeta::Terminate(); // Just in case the client does not. + Terminate_LibUtils(); + + #if UseGlobalLibraryLock & (! XMP_StaticBuild ) + TerminateBasicMutex ( sLibraryLock ); // ! Handled in XMPMeta for static builds. + #endif + + #if XMP_TraceFilesCallsToFile + if ( xmpFilesLog != stderr ) fclose ( xmpFilesLog ); + xmpFilesLog = stderr; + #endif + } // XMPFiles::Terminate // ================================================================================================= @@ -865,7 +872,8 @@ XMPFiles::XMPFiles() : abortProc(0), abortArg(0), handler(0), - handlerTemp(0) + tempPtr(0), + tempUI32(0) { // Nothing more to do, clientRefs is incremented in wrapper. @@ -887,46 +895,13 @@ XMPFiles::~XMPFiles() this->fileRef = 0; } - if ( this->handlerTemp != 0 ) free ( this->handlerTemp ); // ! Must have been malloc-ed! + if ( this->tempPtr != 0 ) free ( this->tempPtr ); // ! Must have been malloc-ed! } // XMPFiles::~XMPFiles // ================================================================================================= /* class static */ -void -XMPFiles::UnlockLib() -{ - - // *** Would be better to have the count in an object with the mutex. - #if TraceXMPLocking - fprintf ( xmpOut, " Unlocking XMPFiles, count = %d\n", sXMPFilesLockCount ); fflush ( xmpOut ); - #endif - --sXMPFilesLockCount; - XMP_Assert ( sXMPFilesLockCount == 0 ); - XMP_ExitCriticalRegion ( sXMPFilesLock ); - -} // XMPFiles::UnlockLib - -// ================================================================================================= - -void -XMPFiles::UnlockObj() -{ - - // *** Would be better to have the count in an object with the mutex. - #if TraceXMPLocking - fprintf ( xmpOut, " Unlocking XMPFiles, count = %d\n", sXMPFilesLockCount ); fflush ( xmpOut ); - #endif - --sXMPFilesLockCount; - XMP_Assert ( sXMPFilesLockCount == 0 ); - XMP_ExitCriticalRegion ( sXMPFilesLock ); - -} // XMPFiles::UnlockObj - -// ================================================================================================= - -/* class static */ bool XMPFiles::GetFormatInfo ( XMP_FileFormat format, XMP_OptionBits * flags /* = 0 */ ) @@ -998,12 +973,6 @@ XMPFiles::OpenFile ( XMP_StringPtr clientPath, XMP_FileFormat format /* = kXMP_UnknownFile */, XMP_OptionBits openFlags /* = 0 */ ) { -#if XMP_UNIXBuild - // *** For the time being only allow the JPEG smart handler for generic UNIX, not even packet scanning. - format = kXMP_JPEGFile; - openFlags |= (kXMPFiles_OpenUseSmartHandler | kXMPFiles_OpenStrictly); -#endif - if ( this->handler != 0 ) XMP_Throw ( "File already open", kXMPErr_BadParam ); if ( this->fileRef != 0 ) { // ! Sanity check to prevent open file leaks. LFA_Close ( this->fileRef ); @@ -1071,7 +1040,6 @@ XMPFiles::OpenFile ( XMP_StringPtr clientPath, } XMP_Assert ( handlerInfo != 0 ); - format = handlerInfo->format; handlerCTor = handlerInfo->handlerCTor; handlerFlags = handlerInfo->flags; @@ -1081,8 +1049,8 @@ XMPFiles::OpenFile ( XMP_StringPtr clientPath, XMP_Assert ( handlerFlags == handler->handlerFlags ); this->handler = handler; - if ( this->format == kXMP_UnknownFile ) this->format = format; // ! The CheckProc might have set it. - + if ( this->format == kXMP_UnknownFile ) this->format = handlerInfo->format; // ! The CheckProc might have set it. + try { handler->CacheFileData(); } catch ( ... ) { @@ -1095,11 +1063,6 @@ XMPFiles::OpenFile ( XMP_StringPtr clientPath, throw; } - if ( ! (openFlags & kXMPFiles_OpenCacheTNail) ) { - handler->containsTNail = false; // Make sure GetThumbnail will cleanly return false. - handler->processedTNail = true; - } - if ( handler->containsXMP ) FillPacketInfo ( handler->xmpPacket, &handler->packetInfo ); if ( (! (openFlags & kXMPFiles_OpenForUpdate)) && (! (handlerFlags & kXMPFiles_HandlerOwnsFile)) ) { @@ -1156,11 +1119,13 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) // Close the file without doing common crash-safe writing. The handler might do it. - #if GatherPerformanceData - if ( needsUpdate ) sAPIPerf->back().extraInfo += ", direct update"; - #endif - - if ( needsUpdate ) this->handler->UpdateFile ( doSafeUpdate ); + if ( needsUpdate ) { + #if GatherPerformanceData + sAPIPerf->back().extraInfo += ", direct update"; + #endif + this->handler->UpdateFile ( doSafeUpdate ); + } + delete this->handler; this->handler = 0; if ( this->fileRef != 0 ) LFA_Close ( this->fileRef ); @@ -1185,19 +1150,22 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) XMP_Assert ( tempFileRef == 0 ); tempFileRef = LFA_Open ( tempFilePath.c_str(), 'w' ); this->fileRef = tempFileRef; + tempFileRef = 0; this->filePath = tempFilePath; this->handler->WriteFile ( origFileRef, origFilePath ); } else { - // The handler can only update an existing file. Do a little dance so the final file - // is the original, thus preserving ownership, permissions, etc. This does have the - // risk that the interim copy under the original name has "current" ownership and - // permissions. The dance steps: - // - Copy the original file to a temp name. - // - Rename the original file to a different temp name. - // - Rename the copy file back to the original name. - // - Call the handler's UpdateFile method for the "original as temp" file. + // The handler can only update an existing file. Do a little dance so that the final + // file is the updated original, thus preserving ownership, permissions, etc. This + // does have the risk that the interim copy under the original name has "current" + // ownership and permissions. The dance steps: + // - Copy the original file to a temp name, the copyFile. + // - Rename the original file to a different temp name, the tempFile. + // - Rename the copyFile back to the original name. + // - Call the handler's UpdateFile method for the tempFile. + // A failure inside the handler's UpdateFile method will leave the copied file under + // the original name. // *** A user abort might leave the copy file under the original name! Need better // *** duplicate code that handles all parts of a file, and for CreateTempFile to @@ -1216,31 +1184,34 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) LFA_Copy ( origFileRef, copyFileRef, fileSize, this->abortProc, this->abortArg ); LFA_Close ( origFileRef ); - origFileRef = this->fileRef = 0; LFA_Close ( copyFileRef ); - copyFileRef = 0; + copyFileRef = origFileRef = this->fileRef = 0; CreateTempFile ( origFilePath, &tempFilePath ); LFA_Delete ( tempFilePath.c_str() ); // ! Slight risk of name being grabbed before rename. LFA_Rename ( origFilePath.c_str(), tempFilePath.c_str() ); + LFA_Rename ( copyFilePath.c_str(), origFilePath.c_str() ); + copyFilePath.clear(); + XMP_Assert ( tempFileRef == 0 ); tempFileRef = LFA_Open ( tempFilePath.c_str(), 'w' ); this->fileRef = tempFileRef; + tempFileRef = 0; + this->filePath = tempFilePath; try { - LFA_Rename ( copyFilePath.c_str(), origFilePath.c_str() ); + this->handler->UpdateFile ( false ); // We're doing the safe update, not the handler. } catch ( ... ) { this->fileRef = 0; + this->filePath = origFilePath; // This is really the copied file. LFA_Close ( tempFileRef ); - LFA_Rename ( tempFilePath.c_str(), origFilePath.c_str() ); + LFA_Delete ( tempFilePath.c_str() ); + tempFileRef = 0; + tempFilePath.clear(); throw; } - XMP_Assert ( (tempFileRef != 0) && (tempFileRef == this->fileRef) ); - this->filePath = tempFilePath; - this->handler->UpdateFile ( false ); // We're doing the safe update, not the handler. - } delete this->handler; @@ -1255,6 +1226,7 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) LFA_Delete ( origFilePath.c_str() ); LFA_Rename ( tempFilePath.c_str(), origFilePath.c_str() ); + tempFilePath.clear(); } @@ -1264,25 +1236,35 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) try { if ( this->fileRef != 0 ) LFA_Close ( this->fileRef ); - } catch ( ... ) { /*Do nothing, throw the outer exception later. */ } + } catch ( ... ) { /* Do nothing, throw the outer exception later. */ } try { if ( origFileRef != 0 ) LFA_Close ( origFileRef ); - } catch ( ... ) { /*Do nothing, throw the outer exception later. */ } + } catch ( ... ) { /* Do nothing, throw the outer exception later. */ } try { if ( tempFileRef != 0 ) LFA_Close ( tempFileRef ); - } catch ( ... ) { /*Do nothing, throw the outer exception later. */ } + } catch ( ... ) { /* Do nothing, throw the outer exception later. */ } + try { + if ( ! tempFilePath.empty() ) LFA_Delete ( tempFilePath.c_str() ); + } catch ( ... ) { /* Do nothing, throw the outer exception later. */ } try { if ( copyFileRef != 0 ) LFA_Close ( copyFileRef ); - } catch ( ... ) { /*Do nothing, throw the outer exception later. */ } + } catch ( ... ) { /* Do nothing, throw the outer exception later. */ } + try { + if ( ! copyFilePath.empty() ) LFA_Delete ( copyFilePath.c_str() ); + } catch ( ... ) { /* Do nothing, throw the outer exception later. */ } try { if ( this->handler != 0 ) delete this->handler; - } catch ( ... ) { /*Do nothing, throw the outer exception later. */ } + } catch ( ... ) { /* Do nothing, throw the outer exception later. */ } this->handler = 0; this->format = kXMP_UnknownFile; this->fileRef = 0; this->filePath.clear(); this->openFlags = 0; + + if ( this->tempPtr != 0 ) free ( this->tempPtr ); // ! Must have been malloc-ed! + this->tempPtr = 0; + this->tempUI32 = 0; throw; @@ -1296,6 +1278,10 @@ XMPFiles::CloseFile ( XMP_OptionBits closeFlags /* = 0 */ ) this->filePath.clear(); this->openFlags = 0; + if ( this->tempPtr != 0 ) free ( this->tempPtr ); // ! Must have been malloc-ed! + this->tempPtr = 0; + this->tempUI32 = 0; + } // XMPFiles::CloseFile // ================================================================================================= @@ -1305,7 +1291,7 @@ XMPFiles::GetFileInfo ( XMP_StringPtr * filePath /* = 0 */, XMP_StringLen * pathLen /* = 0 */, XMP_OptionBits * openFlags /* = 0 */, XMP_FileFormat * format /* = 0 */, - XMP_OptionBits * handlerFlags /* = 0 */ ) + XMP_OptionBits * handlerFlags /* = 0 */ ) const { if ( this->handler == 0 ) return false; XMPFileHandler * handler = this->handler; @@ -1373,6 +1359,8 @@ XMPFiles::GetXMP ( SXMPMeta * xmpObj /* = 0 */, XMP_PacketInfo * packetInfo /* = 0 */ ) { if ( this->handler == 0 ) XMP_Throw ( "XMPFiles::GetXMP - No open file", kXMPErr_BadObject ); + + XMP_OptionBits applyTemplateFlags = kXMPTemplate_AddNewProperties | kXMPTemplate_IncludeInternalProperties; if ( ! this->handler->processedXMP ) { try { @@ -1380,8 +1368,9 @@ XMPFiles::GetXMP ( SXMPMeta * xmpObj /* = 0 */, } catch ( ... ) { // Return the outputs then rethrow the exception. if ( xmpObj != 0 ) { - SXMPUtils::RemoveProperties ( xmpObj, 0, 0, kXMPUtil_DoAllProperties ); - SXMPUtils::AppendProperties ( this->handler->xmpObj, xmpObj, kXMPUtil_DoAllProperties ); + // ! Don't use Clone, that replaces the internal ref in the local xmpObj, leaving the client unchanged! + xmpObj->Erase(); + SXMPUtils::ApplyTemplate ( xmpObj, this->handler->xmpObj, applyTemplateFlags ); } if ( xmpPacket != 0 ) *xmpPacket = this->handler->xmpPacket.c_str(); if ( xmpPacketLen != 0 ) *xmpPacketLen = (XMP_StringLen) this->handler->xmpPacket.size(); @@ -1397,8 +1386,9 @@ XMPFiles::GetXMP ( SXMPMeta * xmpObj /* = 0 */, if ( xmpObj != 0 ) *xmpObj = this->handler->xmpObj.Clone(); #else if ( xmpObj != 0 ) { - SXMPUtils::RemoveProperties ( xmpObj, 0, 0, kXMPUtil_DoAllProperties ); - SXMPUtils::AppendProperties ( this->handler->xmpObj, xmpObj, kXMPUtil_DoAllProperties ); + // ! Don't use Clone, that replaces the internal ref in the local xmpObj, leaving the client unchanged! + xmpObj->Erase(); + SXMPUtils::ApplyTemplate ( xmpObj, this->handler->xmpObj, applyTemplateFlags ); } #endif @@ -1413,23 +1403,6 @@ XMPFiles::GetXMP ( SXMPMeta * xmpObj /* = 0 */, // ================================================================================================= -bool -XMPFiles::GetThumbnail ( XMP_ThumbnailInfo * tnailInfo ) -{ - if ( this->handler == 0 ) XMP_Throw ( "XMPFiles::GetThumbnail - No open file", kXMPErr_BadObject ); - - if ( ! (this->handler->handlerFlags & kXMPFiles_ReturnsTNail) ) return false; - - if ( ! this->handler->processedTNail ) this->handler->ProcessTNail(); - if ( ! this->handler->containsTNail ) return false; - if ( tnailInfo != 0 ) *tnailInfo = this->handler->tnailInfo; - - return true; - -} // XMPFiles::GetThumbnail - -// ================================================================================================= - static bool DoPutXMP ( XMPFiles * thiz, const SXMPMeta & xmpObj, const bool doIt ) { diff --git a/source/XMPFiles/XMPFiles.hpp b/source/XMPFiles/XMPFiles.hpp index b76de70..8744a31 100644 --- a/source/XMPFiles/XMPFiles.hpp +++ b/source/XMPFiles/XMPFiles.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -15,7 +15,12 @@ #define TXMP_STRING_TYPE std::string #include "XMP.hpp" -typedef void * LFA_FileRef; +#if ! UNIX_ENV + typedef void * LFA_FileRef; +#else + typedef XMP_Int32 LFA_FileRef; +#endif + class XMPFileHandler; // ================================================================================================= @@ -167,9 +172,6 @@ public: XMPFiles(); virtual ~XMPFiles(); - static void UnlockLib(); - void UnlockObj(); - static bool GetFormatInfo ( XMP_FileFormat format, XMP_OptionBits * flags = 0 ); @@ -186,7 +188,7 @@ public: XMP_StringLen * filePathLen = 0, XMP_OptionBits * openFlags = 0, XMP_FileFormat * format = 0, - XMP_OptionBits * handlerFlags = 0 ); + XMP_OptionBits * handlerFlags = 0 ) const; void SetAbortProc ( XMP_AbortProc abortProc, void * abortArg ); @@ -195,8 +197,6 @@ public: XMP_StringPtr * xmpPacket = 0, XMP_StringLen * xmpPacketLen = 0, XMP_PacketInfo * packetInfo = 0 ); - - bool GetThumbnail ( XMP_ThumbnailInfo * tnailInfo ); void PutXMP ( const SXMPMeta & xmpObj ); @@ -211,13 +211,16 @@ public: // 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; LFA_FileRef fileRef; // Non-zero if a file is open. std::string filePath; XMP_OptionBits openFlags; XMPFileHandler * handler; // Non-null if a file is open. - void * handlerTemp; // For use between the CheckProc and handler creation. + + void * tempPtr; // For use between the CheckProc and handler creation. + XMP_Uns32 tempUI32; XMP_AbortProc abortProc; void * abortArg; diff --git a/source/XMPFiles/XMPFiles_Impl.cpp b/source/XMPFiles/XMPFiles_Impl.cpp index c07a82f..1677141 100644 --- a/source/XMPFiles/XMPFiles_Impl.cpp +++ b/source/XMPFiles/XMPFiles_Impl.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -43,35 +43,9 @@ using namespace std; #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) #endif -XMP_FileFormat voidFileFormat = 0; // Used as sink for unwanted output parameters. -XMP_Mutex sXMPFilesLock; -int sXMPFilesLockCount = 0; -std::string * sXMPFilesExceptionMessage = 0; - -#if TraceXMPCalls - FILE * xmpFilesOut = stderr; -#endif - -//only define in one non-public-source, non-header(.cpp) place -void LFA_Throw ( const char* msg, int id ) -{ - switch(id) - { - case kLFAErr_InternalFailure: - XMP_Throw(msg,kXMPErr_InternalFailure); - break; - case kLFAErr_ExternalFailure: - XMP_Throw(msg,kXMPErr_ExternalFailure); - break; - case kLFAErr_UserAbort: - XMP_Throw(msg,kXMPErr_UserAbort); - break; - default: - XMP_Throw(msg,kXMPErr_UnknownException); - break; - } -} +bool ignoreLocalText = false; +XMP_FileFormat voidFileFormat = 0; // Used as sink for unwanted output parameters. // ================================================================================================= // Add all known mappings, multiple mappings (tif, tiff) are OK. @@ -145,6 +119,8 @@ const FileExtMapping kFileExtMap[] = { "pdfxml", kXMP_UCFFile }, { "mars", kXMP_UCFFile }, { "idml", kXMP_UCFFile }, + { "idap", kXMP_UCFFile }, + { "icap", kXMP_UCFFile }, { "", 0 } }; // ! Must be last as a sentinel. // Files known to contain XMP but have no smart handling, here or elsewhere. @@ -177,15 +153,32 @@ const char * kKnownRejectedFiles[] = // RAW files "cr2", "erf", "fff", "dcr", "kdc", "mos", "mfw", "mef", "raw", "nef", "orf", "pef", "arw", "sr2", "srf", "sti", - "3fr", - // not supported UCF subformats + "3fr", "rwl", "crw", "sraw", "mos", "mrw", "nrw", "rw2", + "c3f", + // UCF subformats "air", + // Others + "r3d", 0 }; // ! Keep a 0 sentinel at the end. // ================================================================================================= // ================================================================================================= +void LFA_Throw ( const char* msg, int id ) +{ + switch ( id ) { + case kLFAErr_InternalFailure: + XMP_Throw ( msg, kXMPErr_InternalFailure ); + case kLFAErr_ExternalFailure: + XMP_Throw ( msg, kXMPErr_ExternalFailure ); + case kLFAErr_UserAbort: + XMP_Throw ( msg, kXMPErr_UserAbort ); + default: + XMP_Throw ( msg, kXMPErr_UnknownException ); + } +} + // ================================================================================================= #if XMP_MacBuild | XMP_UNIXBuild @@ -198,6 +191,7 @@ const char * kKnownRejectedFiles[] = } #endif +// ================================================================================================= static bool CreateNewFile ( const char * newPath, const char * origPath, size_t filePos, bool copyMacRsrc ) { @@ -748,17 +742,6 @@ void ReadXMPPacket ( XMPFileHandler * handler ) } // ReadXMPPacket // ================================================================================================= -// XMPFileHandler::ProcessTNail -// ============================ - -void XMPFileHandler::ProcessTNail() -{ - - this->processedTNail = true; // ! Must be overridden by handlers that support thumbnails. - -} // XMPFileHandler::ProcessTNail - -// ================================================================================================= // XMPFileHandler::ProcessXMP // ========================== // diff --git a/source/XMPFiles/XMPFiles_Impl.hpp b/source/XMPFiles/XMPFiles_Impl.hpp index 5ba0f14..294ca5f 100644 --- a/source/XMPFiles/XMPFiles_Impl.hpp +++ b/source/XMPFiles/XMPFiles_Impl.hpp @@ -3,7 +3,7 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2004-2008 Adobe Systems Incorporated +// Copyright 2004 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms @@ -13,6 +13,7 @@ #include "XMP_Environment.h" // ! Must be the first #include! #include "XMP_Const.h" #include "XMP_BuildInfo.h" +#include "XMP_LibUtils.hpp" #include "EndianUtils.hpp" #include <string> @@ -30,14 +31,12 @@ #include <cassert> #if XMP_WinBuild - #include <Windows.h> #define snprintf _snprintf #else #if XMP_MacBuild #include <Files.h> #endif // POSIX headers for both Mac and generic UNIX. - #include <pthread.h> #include <fcntl.h> #include <unistd.h> #include <dirent.h> @@ -48,7 +47,9 @@ // ================================================================================================= // General global variables and macros -extern long sXMPFilesInitCount; +extern bool ignoreLocalText; + +extern XMP_Int32 sXMPFilesInitCount; #ifndef GatherPerformanceData #define GatherPerformanceData 0 @@ -67,14 +68,13 @@ extern long sXMPFilesInitCount; kAPIPerf_OpenFile, kAPIPerf_CloseFile, kAPIPerf_GetXMP, - kAPIPerf_GetThumbnail, kAPIPerf_PutXMP, kAPIPerf_CanPutXMP, kAPIPerfProcCount // Last, count of the procs. }; static const char* kAPIPerfNames[] = - { "OpenFile", "CloseFile", "GetXMP", "GetThumbnail", "PutXMP", "CanPutXMP", 0 }; + { "OpenFile", "CloseFile", "GetXMP", "PutXMP", "CanPutXMP", 0 }; struct APIPerfItem { XMP_Uns8 whichProc; @@ -133,10 +133,6 @@ extern const char * kKnownRejectedFiles[]; #define Uns8Ptr(p) ((XMP_Uns8 *) (p)) -#define kTab ((char)0x09) -#define kLF ((char)0x0A) -#define kCR ((char)0x0D) - #define IsNewline( ch ) ( ((ch) == kLF) || ((ch) == kCR) ) #define IsSpaceOrTab( ch ) ( ((ch) == ' ') || ((ch) == kTab) ) #define IsWhitespace( ch ) ( IsSpaceOrTab ( ch ) || IsNewline ( ch ) ) @@ -160,31 +156,40 @@ static inline void MakeUpperCase ( std::string * str ) #define XMP_LitMatch(s,l) (std::strcmp((s),(l)) == 0) #define XMP_LitNMatch(s,l,n) (std::strncmp((s),(l),(n)) == 0) -#define IgnoreParam(p) voidVoidPtr = (void*)&p - // ================================================================================================= -// Support for asserts +// Support for call tracing -#define _MakeStr(p) #p -#define _NotifyMsg(n,c,f,l) #n " failed: " #c " in " f " at line " _MakeStr(l) -#define _NotifyMsg2(msg,c,e) #e " " #msg ": " #c - -#if ! XMP_DebugBuild - #define XMP_Assert(c) ((void) 0) -#else - #define XMP_Assert(c) assert ( c ) +#ifndef XMP_TraceFilesCalls + #define XMP_TraceFilesCalls 0 + #define XMP_TraceFilesCallsToFile 0 #endif - #define XMP_Enforce(c) \ - if ( ! (c) ) { \ - const char * assert_msg = _NotifyMsg ( XMP_Enforce, (c), __FILE__, __LINE__ ); \ - XMP_Throw ( assert_msg , kXMPErr_EnforceFailure ); \ - } -#define XMP_Validate(c,msg,e) \ - if ( ! (c) ) { \ - const char * enforce_msg = _NotifyMsg2(msg,c,e); \ - XMP_Throw ( enforce_msg , e ); \ - } +#if XMP_TraceFilesCalls + + #undef AnnounceThrow + #undef AnnounceCatch + + #undef AnnounceEntry + #undef AnnounceNoLock + #undef AnnounceExit + + extern FILE * xmpFilesLog; + + #define AnnounceThrow(msg) \ + fprintf ( xmpFilesLog, "XMP_Throw: %s\n", msg ); fflush ( xmpFilesLog ) + #define AnnounceCatch(msg) \ + fprintf ( xmpFilesLog, "Catch in %s: %s\n", procName, msg ); fflush ( xmpFilesLog ) + + #define AnnounceEntry(proc) \ + const char * procName = proc; \ + fprintf ( xmpFilesLog, "Entering %s\n", procName ); fflush ( xmpFilesLog ) + #define AnnounceNoLock(proc) \ + const char * procName = proc; \ + fprintf ( xmpFilesLog, "Entering %s (no lock)\n", procName ); fflush ( xmpFilesLog ) + #define AnnounceExit() \ + fprintf ( xmpFilesLog, "Exiting %s\n", procName ); fflush ( xmpFilesLog ) + +#endif // ================================================================================================= // Support for memory leak tracking @@ -214,148 +219,6 @@ static inline void MakeUpperCase ( std::string * str ) #endif // ================================================================================================= -// Support for exceptions and thread locking - -// *** Local copies of threading and exception macros from XMP_Impl.hpp. XMPFiles needs to use a -// *** separate thread lock from XMPCore. Eventually this could benefit from being recast into an -// *** XMPToolkit_Impl that supports separate locks. - -typedef std::string XMP_VarString; - -#ifndef TraceXMPCalls - #define TraceXMPCalls 0 -#endif - -#if ! TraceXMPCalls - - #define AnnounceThrow(msg) /* Do nothing. */ - #define AnnounceCatch(msg) /* Do nothing. */ - - #define AnnounceEntry(proc) /* Do nothing. */ - #define AnnounceNoLock(proc) /* Do nothing. */ - #define AnnounceExit() /* Do nothing. */ - - #define ReportLock() ++sXMPFilesLockCount - #define ReportUnlock() --sXMPFilesLockCount - #define ReportKeepLock() /* Do nothing. */ - -#else - - extern FILE * xmpFilesOut; - - #define AnnounceThrow(msg) \ - fprintf ( xmpFilesOut, "XMP_Throw: %s\n", msg ); fflush ( xmpFilesOut ) - #define AnnounceCatch(msg) \ - fprintf ( xmpFilesOut, "Catch in %s: %s\n", procName, msg ); fflush ( xmpFilesOut ) - - #define AnnounceEntry(proc) \ - const char * procName = proc; \ - fprintf ( xmpFilesOut, "Entering %s\n", procName ); fflush ( xmpFilesOut ) - #define AnnounceNoLock(proc) \ - const char * procName = proc; \ - fprintf ( xmpFilesOut, "Entering %s (no lock)\n", procName ); fflush ( xmpFilesOut ) - #define AnnounceExit() \ - fprintf ( xmpFilesOut, "Exiting %s\n", procName ); fflush ( xmpFilesOut ) - - #define ReportLock() \ - ++sXMPFilesLockCount; fprintf ( xmpFilesOut, " Auto lock, count = %d\n", sXMPFilesLockCount ); fflush ( xmpFilesOut ) - #define ReportUnlock() \ - --sXMPFilesLockCount; fprintf ( xmpFilesOut, " Auto unlock, count = %d\n", sXMPFilesLockCount ); fflush ( xmpFilesOut ) - #define ReportKeepLock() \ - fprintf ( xmpFilesOut, " Keeping lock, count = %d\n", sXMPFilesLockCount ); fflush ( xmpFilesOut ) - -#endif - -#define XMP_Throw(msg,id) { AnnounceThrow ( msg ); throw XMP_Error ( id, msg ); } - -// ------------------------------------------------------------------------------------------------- - -#if XMP_WinBuild - typedef CRITICAL_SECTION XMP_Mutex; -#else - // Use pthread for both Mac and generic UNIX. - typedef pthread_mutex_t XMP_Mutex; -#endif - -extern XMP_Mutex sXMPFilesLock; -extern int sXMPFilesLockCount; // Keep signed to catch unlock errors. -extern XMP_VarString * sXMPFilesExceptionMessage; - -extern bool XMP_InitMutex ( XMP_Mutex * mutex ); -extern void XMP_TermMutex ( XMP_Mutex & mutex ); - -extern void XMP_EnterCriticalRegion ( XMP_Mutex & mutex ); -extern void XMP_ExitCriticalRegion ( XMP_Mutex & mutex ); - -class XMPFiles_AutoMutex { -public: - XMPFiles_AutoMutex() : mutex(&sXMPFilesLock) { XMP_EnterCriticalRegion ( *mutex ); ReportLock(); }; - ~XMPFiles_AutoMutex() { if ( mutex != 0 ) { ReportUnlock(); XMP_ExitCriticalRegion ( *mutex ); mutex = 0; } }; - void KeepLock() { ReportKeepLock(); mutex = 0; }; -private: - XMP_Mutex * mutex; -}; - -// *** Switch to XMPEnterObjectWrapper & XMPEnterStaticWrapper, to allow for per-object locks. - -// ! Don't do the initialization check (sXMP_InitCount > 0) for the no-lock case. That macro is used -// ! by WXMPMeta_Initialize_1. - -#define XMP_ENTER_WRAPPER_NO_LOCK(proc) \ - AnnounceNoLock ( proc ); \ - XMP_Assert ( (0 <= sXMPFilesLockCount) && (sXMPFilesLockCount <= 1) ); \ - try { \ - wResult->errMessage = 0; - -#define XMP_ENTER_WRAPPER(proc) \ - AnnounceEntry ( proc ); \ - XMP_Assert ( sXMPFilesInitCount > 0 ); \ - XMP_Assert ( (0 <= sXMPFilesLockCount) && (sXMPFilesLockCount <= 1) ); \ - try { \ - XMPFiles_AutoMutex mutex; \ - wResult->errMessage = 0; - -#define XMP_EXIT_WRAPPER \ - XMP_CATCH_EXCEPTIONS \ - AnnounceExit(); - -#define XMP_EXIT_WRAPPER_KEEP_LOCK(keep) \ - if ( keep ) mutex.KeepLock(); \ - XMP_CATCH_EXCEPTIONS \ - AnnounceExit(); - -#define XMP_EXIT_WRAPPER_NO_THROW \ - } catch ( ... ) { \ - AnnounceCatch ( "no-throw catch-all" ); \ - /* Do nothing. */ \ - } \ - AnnounceExit(); - -#define XMP_CATCH_EXCEPTIONS \ - } catch ( XMP_Error & xmpErr ) { \ - wResult->int32Result = xmpErr.GetID(); \ - wResult->ptrResult = (void*)"XMP"; \ - wResult->errMessage = xmpErr.GetErrMsg(); \ - if ( wResult->errMessage == 0 ) wResult->errMessage = ""; \ - AnnounceCatch ( wResult->errMessage ); \ - } catch ( std::exception & stdErr ) { \ - wResult->int32Result = kXMPErr_StdException; \ - wResult->errMessage = stdErr.what(); \ - if ( wResult->errMessage == 0 ) wResult->errMessage = ""; \ - AnnounceCatch ( wResult->errMessage ); \ - } catch ( ... ) { \ - wResult->int32Result = kXMPErr_UnknownException; \ - wResult->errMessage = "Caught unknown exception"; \ - AnnounceCatch ( wResult->errMessage ); \ - } - -#if XMP_DebugBuild - #define RELEASE_NO_THROW /* empty */ -#else - #define RELEASE_NO_THROW throw() -#endif - -// ================================================================================================= // FileHandler declarations extern void ReadXMPPacket ( XMPFileHandler * handler ); @@ -367,7 +230,6 @@ public: #define DefaultCTorPresets \ handlerFlags(0), stdCharForm(kXMP_CharUnknown), \ - containsTNail(false), processedTNail(false), \ containsXMP(false), processedXMP(false), needsUpdate(false) XMPFileHandler() : parent(0), DefaultCTorPresets {}; @@ -376,7 +238,6 @@ public: virtual ~XMPFileHandler() {}; // ! The specific handler is responsible for tnailInfo.tnailImage. virtual void CacheFileData() = 0; - virtual void ProcessTNail(); // The default implementation just sets processedTNail to true. virtual void ProcessXMP(); // The default implementation just parses the XMP. virtual XMP_OptionBits GetSerializeOptions(); // The default is compact. @@ -390,8 +251,6 @@ public: XMP_OptionBits handlerFlags; // Capabilities of this handler. XMP_Uns8 stdCharForm; // The standard character form for output. - bool containsTNail; // True if the file has a native thumbnail. - bool processedTNail; // True if the cached thumbnail data has been processed. bool containsXMP; // True if the file has XMP or PutXMP has been called. bool processedXMP; // True if the XMP is parsed and reconciled. bool needsUpdate; // True if the file needs to be updated. @@ -400,8 +259,6 @@ public: std::string xmpPacket; // ! This is the current XMP, updated by XMPFiles::PutXMP. SXMPMeta xmpObj; - XMP_ThumbnailInfo tnailInfo; - }; // XMPFileHandler typedef XMPFileHandler * (* XMPFileHandlerCTor) ( XMPFiles * parent ); |