diff options
Diffstat (limited to 'source/XMPFiles/FileHandlers')
50 files changed, 5659 insertions, 4200 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; |