diff options
Diffstat (limited to 'source/XMPFiles/FileHandlers')
38 files changed, 9965 insertions, 214 deletions
diff --git a/source/XMPFiles/FileHandlers/ASF_Handler.cpp b/source/XMPFiles/FileHandlers/ASF_Handler.cpp new file mode 100644 index 0000000..5726533 --- /dev/null +++ b/source/XMPFiles/FileHandlers/ASF_Handler.cpp @@ -0,0 +1,362 @@ +// ================================================================================================= +// 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 "ASF_Handler.hpp" + +// ================================================================================================= +/// \file ASF_Handler.hpp +/// \brief File format handler for ASF. +/// +/// This handler ... +/// +// ================================================================================================= + +// ================================================================================================= +// ASF_MetaHandlerCTor +// ==================== + +XMPFileHandler * ASF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new ASF_MetaHandler ( parent ); + +} // ASF_MetaHandlerCTor + +// ================================================================================================= +// ASF_CheckFormat +// =============== + +bool ASF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ) +{ + + IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent); + XMP_Assert ( format == kXMP_WMAVFile ); + + IOBuffer ioBuf; + + LFA_Seek ( fileRef, 0, SEEK_SET ); + if ( ! CheckFileSpace ( fileRef, &ioBuf, guidLen ) ) return false; + + GUID guid; + memcpy ( &guid, ioBuf.ptr, guidLen ); + + if ( ! IsEqualGUID ( ASF_Header_Object, guid ) ) return false; + + return true; + +} // ASF_CheckFormat + +// ================================================================================================= +// ASF_MetaHandler::ASF_MetaHandler +// ================================== + +ASF_MetaHandler::ASF_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; + this->handlerFlags = kASF_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} + +// ================================================================================================= +// ASF_MetaHandler::~ASF_MetaHandler +// =================================== + +ASF_MetaHandler::~ASF_MetaHandler() +{ + // Nothing extra to do. +} + +// ================================================================================================= +// ASF_MetaHandler::CacheFileData +// =============================== + +void ASF_MetaHandler::CacheFileData() +{ + + this->containsXMP = false; + + LFA_FileRef fileRef ( this->parent->fileRef ); + if ( fileRef == 0 ) return; + + ASF_Support support ( &this->legacyManager ); + ASF_Support::ObjectState objectState; + long numTags = support.OpenASF ( fileRef, objectState ); + if ( numTags == 0 ) return; + + if ( objectState.xmpLen != 0 ) { + + // XMP present + + XMP_Int32 len = XMP_Int32 ( objectState.xmpLen ); + + this->xmpPacket.reserve( len ); + this->xmpPacket.assign ( len, ' ' ); + + bool found = ASF_Support::ReadBuffer ( fileRef, objectState.xmpPos, objectState.xmpLen, + const_cast<char *>(this->xmpPacket.data()) ); + if ( found ) { + this->packetInfo.offset = objectState.xmpPos; + this->packetInfo.length = len; + this->containsXMP = true; + } + + } + +} // ASF_MetaHandler::CacheFileData + +// ================================================================================================= +// ASF_MetaHandler::ProcessTNail +// ============================== + +void ASF_MetaHandler::ProcessTNail() +{ + + XMP_Throw ( "ASF_MetaHandler::ProcessTNail isn't implemented yet", kXMPErr_Unimplemented ); + +} // ASF_MetaHandler::ProcessTNail + +// ================================================================================================= +// ASF_MetaHandler::ProcessXMP +// ============================ +// +// Process the raw XMP and legacy metadata that was previously cached. + +void ASF_MetaHandler::ProcessXMP() +{ + + this->processedXMP = true; // Make sure we only come through here once. + + // Process the XMP packet. + + if ( this->xmpPacket.empty() ) { + + // import legacy in any case, when no XMP present + legacyManager.ImportLegacy ( &this->xmpObj ); + this->legacyManager.SetDigest ( &this->xmpObj ); + + } else { + + XMP_Assert ( this->containsXMP ); + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); + + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + + if ( ! legacyManager.CheckDigest ( this->xmpObj ) ) { + legacyManager.ImportLegacy ( &this->xmpObj ); + } + + } + + // Assume we now have something in the XMP. + this->containsXMP = true; + +} // ASF_MetaHandler::ProcessXMP + +// ================================================================================================= +// ASF_MetaHandler::UpdateFile +// ============================ + +void ASF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + + bool updated = false; + + if ( ! this->needsUpdate ) return; + + LFA_FileRef fileRef ( this->parent->fileRef ); + if ( fileRef == 0 ) return; + + ASF_Support support; + ASF_Support::ObjectState objectState; + long numTags = support.OpenASF ( fileRef, objectState ); + if ( numTags == 0 ) return; + + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); + + this->legacyManager.ExportLegacy ( this->xmpObj ); + if ( this->legacyManager.hasLegacyChanged() ) { + + this->legacyManager.SetDigest ( &this->xmpObj ); + + // serialize with updated digest + if ( objectState.xmpLen == 0 ) { + + // XMP does not exist, use standard padding + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + + } else { + + // re-use padding with static XMP size + try { + XMP_OptionBits compactExact = (kXMP_UseCompactFormat | kXMP_ExactPacketLength); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, compactExact, XMP_StringLen(objectState.xmpLen) ); + } catch ( ... ) { + // re-use padding with exact packet length failed (legacy-digest needed too much space): try again using standard padding + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + } + + } + + XMP_StringPtr packetStr = xmpPacket.c_str(); + packetLen = (XMP_StringLen)xmpPacket.size(); + if ( packetLen == 0 ) return; + + // value, when guessing for sufficient legacy padding (line-ending conversion etc.) + const int paddingTolerance = 50; + + bool xmpGrows = ( objectState.xmpLen && (packetLen > objectState.xmpLen) && ( ! objectState.xmpIsLastObject) ); + + bool legacyGrows = ( this->legacyManager.hasLegacyChanged() && + (this->legacyManager.getLegacyDiff() > (this->legacyManager.GetPadding() - paddingTolerance)) ); + + if ( doSafeUpdate || legacyGrows || xmpGrows ) { + + // do a safe update in any case + updated = SafeWriteFile(); + + } else { + + // possibly we can do an in-place update + + if ( objectState.xmpLen < packetLen ) { + + updated = SafeWriteFile(); + + } else { + + // current XMP chunk size is sufficient -> write (in place update) + updated = ASF_Support::WriteBuffer(fileRef, objectState.xmpPos, packetLen, packetStr ); + + // legacy update + if ( updated && this->legacyManager.hasLegacyChanged() ) { + + ASF_Support::ObjectIterator curPos = objectState.objects.begin(); + ASF_Support::ObjectIterator endPos = objectState.objects.end(); + + for ( ; curPos != endPos; ++curPos ) { + + ASF_Support::ObjectData object = *curPos; + + // find header-object + if ( IsEqualGUID ( ASF_Header_Object, object.guid ) ) { + // update header object + updated = support.UpdateHeaderObject ( fileRef, object, legacyManager ); + } + + } + + } + + } + + } + + if ( ! updated ) return; // If there's an error writing the chunk, bail. + + this->needsUpdate = false; + +} // ASF_MetaHandler::UpdateFile + +// ================================================================================================= +// ASF_MetaHandler::WriteFile +// =========================== + +void ASF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) +{ + LFA_FileRef destRef = this->parent->fileRef; + + ASF_Support support; + ASF_Support::ObjectState objectState; + long numTags = support.OpenASF ( sourceRef, objectState ); + if ( numTags == 0 ) return; + + LFA_Truncate ( destRef, 0 ); + + ASF_Support::ObjectIterator curPos = objectState.objects.begin(); + ASF_Support::ObjectIterator endPos = objectState.objects.end(); + + for ( ; curPos != endPos; ++curPos ) { + + ASF_Support::ObjectData object = *curPos; + + // discard existing XMP object + if ( object.xmp ) continue; + + // update header-object, when legacy needs update + if ( IsEqualGUID ( ASF_Header_Object, object.guid) && this->legacyManager.hasLegacyChanged( ) ) { + // rewrite header object + support.WriteHeaderObject ( sourceRef, destRef, object, this->legacyManager, false ); + } else { + // copy any other object + ASF_Support::CopyObject ( sourceRef, destRef, object ); + } + + // write XMP object immediately after the (one and only) top-level DataObject + if ( IsEqualGUID ( ASF_Data_Object, object.guid ) ) { + XMP_StringPtr packetStr = xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); + ASF_Support::WriteXMPObject ( destRef, packetLen, packetStr ); + } + + } + + support.UpdateFileSize ( destRef ); + +} // ASF_MetaHandler::WriteFile + +// ================================================================================================= +// ASF_MetaHandler::SafeWriteFile +// =========================== + +bool ASF_MetaHandler::SafeWriteFile () +{ + bool ret = false; + + 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 { + this->WriteFile ( origRef, origPath ); + ret = true; + } catch ( ... ) { + LFA_Close ( updateRef ); + 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; + + return ret; + +} // ASF_MetaHandler::SafeWriteFile + +// ================================================================================================= diff --git a/source/XMPFiles/FileHandlers/ASF_Handler.hpp b/source/XMPFiles/FileHandlers/ASF_Handler.hpp new file mode 100644 index 0000000..9f54b15 --- /dev/null +++ b/source/XMPFiles/FileHandlers/ASF_Handler.hpp @@ -0,0 +1,66 @@ +#ifndef __ASF_Handler_hpp__ +#define __ASF_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" +#include "ASF_Support.hpp" + +// ================================================================================================= +/// \file ASF_Handler.hpp +/// \brief File format handler for ASF. +/// +/// This header ... +/// +// ================================================================================================= + +// *** Could derive from Basic_Handler - buffer file tail in a temp file. + +extern XMPFileHandler* ASF_MetaHandlerCTor ( XMPFiles* parent ); + +extern bool ASF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles* parent ); + +static const XMP_OptionBits kASF_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_NeedsReadOnlyPacket + ); + +class ASF_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessTNail(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string& sourcePath ); + + bool SafeWriteFile (); + + ASF_MetaHandler ( XMPFiles* parent ); + virtual ~ASF_MetaHandler(); + +private: + + ASF_LegacyManager legacyManager; + +}; // ASF_MetaHandler + +// ================================================================================================= + +#endif /* __ASF_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/AVCHD_Handler.cpp b/source/XMPFiles/FileHandlers/AVCHD_Handler.cpp new file mode 100644 index 0000000..63b8f29 --- /dev/null +++ b/source/XMPFiles/FileHandlers/AVCHD_Handler.cpp @@ -0,0 +1,648 @@ +// ================================================================================================= +// 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 "AVCHD_Handler.hpp" + +#include "MD5.h" + +using namespace std; + +// ================================================================================================= +/// \file AVCHD_Handler.cpp +/// \brief Folder format handler for AVCHD. +/// +/// This handler is for the AVCHD video format. +/// +/// A typical AVCHD layout looks like: +/// +/// BDMV/ +/// index.bdmv +/// MovieObject.bdmv +/// PLAYLIST/ +/// 00000.mpls +/// 00001.mpls +/// STREAM/ +/// 00000.m2ts +/// 00001.m2ts +/// CLIPINF/ +/// 00000.clpi +/// 00001.clpi +/// BACKUP/ +/// +// ================================================================================================= + +// ================================================================================================= + +// AVCHD Format. Book 1: Playback System Basic Specifications V 1.01. p. 76 + +struct AVCHD_blkProgramInfo +{ + XMP_Uns32 mLength; + XMP_Uns8 mReserved1[2]; + XMP_Uns32 mSPNProgramSequenceStart; + XMP_Uns16 mProgramMapPID; + XMP_Uns8 mNumberOfStreamsInPS; + XMP_Uns8 mReserved2; + + // Video stream. + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mVideoFormat; + XMP_Uns8 mFrameRate; + XMP_Uns8 mAspectRatio; + XMP_Uns8 mCCFlag; + } mVideoStream; + + // Audio stream. + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mAudioPresentationType; + XMP_Uns8 mSamplingFrequency; + XMP_Uns8 mAudioLanguageCode[4]; + } mAudioStream; + + // Pverlay bitmap stream. + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mOBLanguageCode[4]; + } mOverlayBitmapStream; + + // Menu bitmap stream. + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mBMLanguageCode[4]; + } mMenuBitmapStream; + +}; + +// ================================================================================================= +// AVCHD_CheckFormat +// ================= +// +// This version checks for the presence of a top level BPAV directory, and the required files and +// directories immediately within it. The CLIPINF, PLAYLIST, and STREAM subfolders are required, as +// are the index.bdmv and MovieObject.bdmv files. +// +// The state of the string parameters depends on the form of the path passed by the client. If the +// client passed a logical clip path, like ".../MyMovie/00001", the parameters are: +// rootPath - ".../MyMovie" +// gpName - empty +// parentName - empty +// leafName - "00001" +// If the client passed a full file path, like ".../MyMovie/BDMV/CLIPINF/00001.clpi", they are: +// rootPath - ".../MyMovie" +// gpName - "BDMV" +// parentName - "CLIPINF" or "PALYLIST" or "STREAM" +// leafName - "00001" + +// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +// ! Using explicit '/' as a separator when creating paths, it works on Windows. + +// ! Sample files show that the ".bdmv" extension can sometimes be ".bdm". Allow either. + +bool AVCHD_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ) +{ + if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty. + + if ( ! gpName.empty() ) { + if ( gpName != "BDMV" ) return false; + if ( (parentName != "CLIPINF") && (parentName != "PLAYLIST") && (parentName != "STREAM") ) return false; + } + + // Check the rest of the required general structure. Look for both ".bdmv" and ".bmd" extensions. + + std::string bdmvPath ( rootPath ); + bdmvPath += kDirChar; + bdmvPath += "BDMV"; + + if ( GetChildMode ( bdmvPath, "CLIPINF" ) != kFMode_IsFolder ) return false; + if ( GetChildMode ( bdmvPath, "PLAYLIST" ) != kFMode_IsFolder ) return false; + if ( GetChildMode ( bdmvPath, "STREAM" ) != kFMode_IsFolder ) return false; + + if ( (GetChildMode ( bdmvPath, "index.bdmv" ) != kFMode_IsFile) && + (GetChildMode ( bdmvPath, "index.bdm" ) != kFMode_IsFile) ) return false; + + if ( (GetChildMode ( bdmvPath, "MovieObject.bdmv" ) != kFMode_IsFile) && + (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; + } + + // 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 ); + + return true; + +} // AVCHD_CheckFormat + +// ================================================================================================= +// ReadAVCHDProgramInfo +// ==================== + +static bool ReadAVCHDProgramInfo ( const std::string& strPath, AVCHD_blkProgramInfo& avchdProgramInfo ) +{ + try { + + AutoFile idxFile; + idxFile.fileRef = LFA_Open ( strPath.c_str(), 'r' ); + if ( idxFile.fileRef == 0 ) return false; // The open failed. + + memset ( &avchdProgramInfo, 0, sizeof(AVCHD_blkProgramInfo) ); + + // Read clip header. (AVCHD Format. Book1. v. 1.01. p 64 ) + struct AVCHD_ClipInfoHeader + { + XMP_Uns8 mTypeIndicator[4]; + XMP_Uns8 mTypeIndicator2[4]; + XMP_Uns32 mSequenceInfoStartAddress; + XMP_Uns32 mProgramInfoStartAddress; + XMP_Uns32 mCPIStartAddress; + 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 ); + + // Seek to the program header. (AVCHD Format. Book1. v. 1.01. p 77 ) + LFA_Seek ( idxFile.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 ); + + XMP_Uns16 streamPID = 0; + for ( int i=0; i<avchdProgramInfo.mNumberOfStreamsInPS; ++i ) { + + XMP_Uns8 length = 0; + XMP_Uns8 streamCodingType = 0; + + streamPID = LFA_ReadUns16_BE ( idxFile.fileRef ); + LFA_Read ( idxFile.fileRef, &length, 1 ); + + XMP_Int64 pos = LFA_Tell ( idxFile.fileRef ); + + LFA_Read ( idxFile.fileRef, &streamCodingType, 1 ); + + 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 + + LFA_Read ( idxFile.fileRef, avchdProgramInfo.mAudioStream.mAudioLanguageCode, 3 ); + avchdProgramInfo.mAudioStream.mAudioLanguageCode[3] = 0; + + avchdProgramInfo.mAudioStream.mPresent = 1; + } + 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; + + case 0x91 : // Menu bitmap stream. + LFA_Read ( idxFile.fileRef, &avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode, 3 ); + avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode[3] = 0; + avchdProgramInfo.mMenuBitmapStream.mPresent = 1; + break; + + default : + break; + + } + + LFA_Seek ( idxFile.fileRef, pos + length, SEEK_SET ); + + } + + } catch ( ... ) { + + return false; + + } + + return true; + +} // ReadAVCHDProgramInfo + +// ================================================================================================= +// AVCHD_MetaHandlerCTor +// ===================== + +XMPFileHandler * AVCHD_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new AVCHD_MetaHandler ( parent ); + +} // AVCHD_MetaHandlerCTor + +// ================================================================================================= +// AVCHD_MetaHandler::AVCHD_MetaHandler +// ==================================== + +AVCHD_MetaHandler::AVCHD_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kAVCHD_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + // Extract the root path and clip name. + + XMP_Assert ( this->parent->handlerTemp != 0 ); + + this->rootPath.assign ( (char*) this->parent->handlerTemp ); + free ( this->parent->handlerTemp ); + this->parent->handlerTemp = 0; + + SplitLeafName ( &this->rootPath, &this->clipName ); + +} // AVCHD_MetaHandler::AVCHD_MetaHandler + +// ================================================================================================= +// AVCHD_MetaHandler::~AVCHD_MetaHandler +// ===================================== + +AVCHD_MetaHandler::~AVCHD_MetaHandler() +{ + + if ( this->parent->handlerTemp != 0 ) { + free ( this->parent->handlerTemp ); + this->parent->handlerTemp = 0; + } + +} // AVCHD_MetaHandler::~AVCHD_MetaHandler + +// ================================================================================================= +// AVCHD_MetaHandler::MakeClipInfoPath +// =================================== + +void AVCHD_MetaHandler::MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix ) +{ + + *path = this->rootPath; + *path += kDirChar; + *path += "BDMV"; + *path += kDirChar; + *path += "CLIPINF"; + *path += kDirChar; + *path += this->clipName; + *path += suffix; + +} // AVCHD_MetaHandler::MakeClipInfoPath + +// ================================================================================================= +// AVCHD_MetaHandler::MakeClipStreamPath +// ===================================== + +void AVCHD_MetaHandler::MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix ) +{ + + *path = this->rootPath; + *path += kDirChar; + *path += "BDMV"; + *path += kDirChar; + *path += "STREAM"; + *path += kDirChar; + *path += this->clipName; + *path += suffix; + +} // AVCHD_MetaHandler::MakeClipStreamPath + +// ================================================================================================= +// AVCHD_MetaHandler::MakeLegacyDigest +// =================================== + +#define kHexDigits "0123456789ABCDEF" + +void AVCHD_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + AVCHD_blkProgramInfo avchdProgramInfo; + std::string strPath; + this->MakeClipInfoPath ( &strPath, ".clpi" ); + + if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) { + this->MakeClipInfoPath ( &strPath, ".cpi" ); + if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) return; + } + + MD5_CTX context; + unsigned char digestBin [16]; + + MD5Init ( &context ); + MD5Update ( &context, (XMP_Uns8*)&avchdProgramInfo, (unsigned int) sizeof(avchdProgramInfo) ); + 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 ); + +} // AVCHD_MetaHandler::MakeLegacyDigest + +// ================================================================================================= +// AVCHD_MetaHandler::CacheFileData +// ================================ + +void AVCHD_MetaHandler::CacheFileData() +{ + XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + + // 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. + + // Read the entire .XMP file. + + char openMode = 'r'; + if ( this->parent->openFlags & kXMPFiles_OpenForUpdate ) openMode = 'w'; + + LFA_FileRef xmpFile = LFA_Open ( xmpPath.c_str(), openMode ); + if ( xmpFile == 0 ) return; // The open failed. + + XMP_Int64 xmpLen = LFA_Measure ( xmpFile ); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "AVCHD XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.reserve ( (size_t ) xmpLen ); + this->xmpPacket.append ( (size_t ) xmpLen, ' ' ); + + XMP_Int32 ioCount = LFA_Read ( xmpFile, (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen, kLFA_RequireAll ); + XMP_Assert ( ioCount == xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + XMP_Assert ( this->parent->fileRef == 0 ); + if ( openMode == 'r' ) { + LFA_Close ( xmpFile ); + } else { + this->parent->fileRef = xmpFile; + } + + this->containsXMP = true; + +} // AVCHD_MetaHandler::CacheFileData + +// ================================================================================================= +// AVCHD_MetaHandler::ProcessXMP +// ============================= + +void AVCHD_MetaHandler::ProcessXMP() +{ + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + // read clip info + AVCHD_blkProgramInfo avchdProgramInfo; + std::string strPath; + this->MakeClipInfoPath ( &strPath, ".clpi" ); + + if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) { + this->MakeClipInfoPath ( &strPath, ".cpi" ); + if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) return; + } + + // Video Stream. AVCHD Format v. 1.01 p. 78 + + 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 ); + } + + // XMP videoFrameSize. + xmpValue = 0; + int frameIndex = -1; + 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 + default: break; + } + if ( frameIndex != -1 ) { + xmpValue = frameWidth[frameIndex]; + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", xmpValue, 0 ); + xmpValue = frameHeight[frameIndex]; + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", xmpValue, 0 ); + xmpValue = "pixels"; + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", xmpValue, 0 ); + } + + this->containsXMP = true; + + } + + // Audio Stream. + if ( avchdProgramInfo.mAudioStream.mPresent ) { + + xmpValue = 0; + switch ( avchdProgramInfo.mAudioStream.mAudioPresentationType ) { + case 1 : xmpValue = "Mono"; break; + case 3 : xmpValue = "Stereo"; break; + default : break; + } + if ( xmpValue != 0 ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "audioChannelType", xmpValue, kXMP_DeleteExisting ); + } + + xmpValue = 0; + switch ( avchdProgramInfo.mAudioStream.mSamplingFrequency ) { + case 1 : xmpValue = "48000"; break; + case 4 : xmpValue = "96000"; break; + case 5 : xmpValue = "192000"; break; + default : break; + } + if ( xmpValue != 0 ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "audioSampleRate", xmpValue, kXMP_DeleteExisting ); + } + + this->containsXMP = true; + + } + +} // AVCHD_MetaHandler::ProcessXMP + +// ================================================================================================= +// AVCHD_MetaHandler::UpdateFile +// ============================= +// +// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here. + +void AVCHD_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Make sure only called once. + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "AVCHD", newDigest.c_str(), kXMP_DeleteExisting ); + + LFA_FileRef oldFile = this->parent->fileRef; + + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() ); + + if ( oldFile == 0 ) { + + // The XMP does not exist yet. + + std::string xmpPath; + this->MakeClipStreamPath ( &xmpPath, ".xmp" ); + + LFA_FileRef xmpFile = LFA_Create ( xmpPath.c_str() ); + if ( xmpFile == 0 ) XMP_Throw ( "Failure creating AVCHD XMP file", kXMPErr_ExternalFailure ); + LFA_Write ( xmpFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( xmpFile ); + + } else if ( ! doSafeUpdate ) { + + // Over write the existing XMP file. + + LFA_Seek ( oldFile, 0, SEEK_SET ); + LFA_Truncate ( oldFile, 0 ); + LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( oldFile ); + + } else { + + // Do a safe update. + + // *** We really need an LFA_SwapFiles utility. + + std::string xmpPath, tempPath; + + this->MakeClipStreamPath ( &xmpPath, ".xmp" ); + + CreateTempFile ( xmpPath, &tempPath ); + LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' ); + LFA_Write ( tempFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( tempFile ); + + LFA_Close ( oldFile ); + LFA_Delete ( xmpPath.c_str() ); + LFA_Rename ( tempPath.c_str(), xmpPath.c_str() ); + + } + + this->parent->fileRef = 0; + +} // AVCHD_MetaHandler::UpdateFile + +// ================================================================================================= +// AVCHD_MetaHandler::WriteFile +// ============================ + +void AVCHD_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) +{ + + // ! WriteFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "AVCHD_MetaHandler::WriteFile should not be called", kXMPErr_InternalFailure ); + +} // AVCHD_MetaHandler::WriteFile + +// ================================================================================================= diff --git a/source/XMPFiles/FileHandlers/AVCHD_Handler.hpp b/source/XMPFiles/FileHandlers/AVCHD_Handler.hpp new file mode 100644 index 0000000..45e9cf9 --- /dev/null +++ b/source/XMPFiles/FileHandlers/AVCHD_Handler.hpp @@ -0,0 +1,77 @@ +#ifndef __AVCHD_Handler_hpp__ +#define __AVCHD_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. + +#include "XMPFiles_Impl.hpp" + +#include "ExpatAdapter.hpp" + +// ================================================================================================= +/// \file AVCHD_Handler.hpp +/// \brief Folder format handler for AVCHD. +/// +/// This header ... +/// +// ================================================================================================= + +extern XMPFileHandler * AVCHD_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool AVCHD_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kAVCHD_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + +class AVCHD_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files. + { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); }; + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); + + AVCHD_MetaHandler ( XMPFiles * _parent ); + virtual ~AVCHD_MetaHandler(); + +private: + + AVCHD_MetaHandler() {}; // Hidden on purpose. + + void MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix ); + void MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix ); + void MakeLegacyDigest ( std::string * digestStr ); + + std::string rootPath, clipName; + +}; // AVCHD_MetaHandler + +// ================================================================================================= + +#endif /* __AVCHD_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/AVI_Handler.cpp b/source/XMPFiles/FileHandlers/AVI_Handler.cpp index 6511246..96a11ec 100644 --- a/source/XMPFiles/FileHandlers/AVI_Handler.cpp +++ b/source/XMPFiles/FileHandlers/AVI_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 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 @@ -20,15 +20,10 @@ #endif #endif - using namespace std; #define kXMPUserDataType MakeFourCC ( '_', 'P', 'M', 'X' ) /* Yes, backwards! */ - -/******************************************************* -** Premiere Pro specific info for reconciliation -*******************************************************/ // FourCC codes for the RIFF chunks #define aviTimeChunk MakeFourCC('I','S','M','T') #define avihdrlChunk MakeFourCC('h','d','r','l') @@ -57,10 +52,6 @@ using namespace std; #define kAltTapeName "altTapeName" #define kLogComment "logComment" -/******************************************************* -*******************************************************/ - - // ================================================================================================= /// \file AVI_Handler.cpp /// \brief File format handler for AVI. @@ -144,14 +135,14 @@ void AVI_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( doSafeUpdate ) XMP_Throw ( "AVI_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); XMP_StringPtr packetStr = xmpPacket.c_str(); - XMP_StringLen packetLen = xmpPacket.size(); + 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 = xmpPacket.size(); + packetLen = (XMP_StringLen)xmpPacket.size(); LFA_FileRef fileRef(this->parent->fileRef); if ( fileRef == 0 ) return; @@ -161,7 +152,10 @@ void AVI_MetaHandler::UpdateFile ( bool doSafeUpdate ) 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. + 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 @@ -185,7 +179,7 @@ void AVI_MetaHandler::UpdateFile ( bool doSafeUpdate ) ok = FindChunk ( riffState, myCommentChunk, myCommentList, 0, 0, 0, 0 ); - if ( ! ok ) { + 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() ); @@ -225,19 +219,29 @@ void AVI_MetaHandler::UpdateFile ( bool doSafeUpdate ) } else { - 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 ); + // 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 ); + + } } @@ -259,6 +263,24 @@ void AVI_MetaHandler::WriteFile ( LFA_FileRef sourceRef, } // 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 // ============================== @@ -268,16 +290,48 @@ void AVI_MetaHandler::CacheFileData() this->containsXMP = false; - LFA_FileRef fileRef ( this->parent->fileRef ); + 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; + 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, 0, 0, 0, &bufferSize ); + ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType /* _PMX, the xmp packet */, 0, 0, 0, &bufferSize); if ( ! ok ) { @@ -290,22 +344,26 @@ void AVI_MetaHandler::CacheFileData() this->xmpPacket.assign ( bufferSize, ' ' ); // Get the metadata - ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType, 0, 0, (char*)this->xmpPacket.c_str(), &bufferSize ); + 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 = kXMPFiles_UnknownOffset; + this->packetInfo.offset = xmpPacketPosition; this->packetInfo.length = bufferSize; - this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), this->xmpPacket.size() ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); this->containsXMP = true; } } - // Reconcile legacy metadata. - - std::string aviTimeString, orgTimeString, altTimeString; + + 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 ); @@ -422,11 +480,19 @@ void AVI_MetaHandler::CacheFileData() } - // Update the xmpPacket, as the xmpObj might have been updated with legacy info. - this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); - this->packetInfo.offset = kXMPFiles_UnknownOffset; - this->packetInfo.length = this->xmpPacket.size(); + 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/Basic_Handler.cpp b/source/XMPFiles/FileHandlers/Basic_Handler.cpp index 845fe16..f3b471e 100644 --- a/source/XMPFiles/FileHandlers/Basic_Handler.cpp +++ b/source/XMPFiles/FileHandlers/Basic_Handler.cpp @@ -71,7 +71,7 @@ void Basic_MetaHandler::UpdateFile ( bool doSafeUpdate ) LFA_Seek ( fileRef, 0, SEEK_END ); this->WriteXMPPrefix(); - LFA_Write ( fileRef, xmpPacket.c_str(), xmpPacket.size() ); + LFA_Write ( fileRef, xmpPacket.c_str(), (XMP_StringLen)xmpPacket.size() ); this->WriteXMPSuffix(); if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "Basic_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort ); @@ -135,7 +135,7 @@ void Basic_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & s // Write the new XMP section to the destination. this->WriteXMPPrefix(); - LFA_Write ( destRef, this->xmpPacket.c_str(), this->xmpPacket.size() ); + LFA_Write ( destRef, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); this->WriteXMPSuffix(); if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "Basic_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); diff --git a/source/XMPFiles/FileHandlers/Basic_Handler.hpp b/source/XMPFiles/FileHandlers/Basic_Handler.hpp index 4f8dc77..45eef9f 100644 --- a/source/XMPFiles/FileHandlers/Basic_Handler.hpp +++ b/source/XMPFiles/FileHandlers/Basic_Handler.hpp @@ -57,6 +57,7 @@ static const XMP_OptionBits kBasic_HandlerFlags = (kXMPFiles_CanInjectXMP | kXMPFiles_CanExpand | kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | kXMPFiles_AllowsSafeUpdate); diff --git a/source/XMPFiles/FileHandlers/FLV_Handler.cpp b/source/XMPFiles/FileHandlers/FLV_Handler.cpp new file mode 100644 index 0000000..2472870 --- /dev/null +++ b/source/XMPFiles/FileHandlers/FLV_Handler.cpp @@ -0,0 +1,750 @@ +// ================================================================================================= +// 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 "FLV_Handler.hpp" + +#include "MD5.h" + +using namespace std; + +// ================================================================================================= +/// \file FLV_Handler.cpp +/// \brief File format handler for FLV. +/// +/// FLV is a fairly simple format, with a strong orientation to streaming use. It consists of a +/// small file header then a sequence of tags that can contain audio data, video data, or +/// ActionScript data. All integers in FLV are big endian. +/// +/// For FLV version 1, the file header contains: +/// +/// UI24 signature - the characters "FLV" +/// UI8 version - 1 +/// UI8 flags - 0x01 = has video tags, 0x04 = has audio tags +/// UI32 length in bytes of file header +/// +/// For FLV version 1, each tag begins with an 11 byte header: +/// +/// UI8 tag type - 8 = audio tag, 9 = video tag, 18 = script data tag +/// UI24 content length in bytes +/// UI24 time - low order 3 bytes +/// UI8 time - high order byte +/// UI24 stream ID +/// +/// This is followed by the tag's content, then a UI32 "back pointer" which is the header size plus +/// the content size. A UI32 zero is placed between the file header and the first tag as a +/// terminator for backward scans. The time in a tag header is the start of playback for that tag. +/// The tags must be in ascending time order. For a given time it is preferred that script data tags +/// precede audio and video tags. +/// +/// For metadata purposes only the script data tags are of interest. Script data information becomes +/// accessible to ActionScript at the playback moment of the script data tag through a call to a +/// registered data handler. The content of a script data tag contains a string and an ActionScript +/// data value. The string is the name of the handler to be invoked, the data value is passed as an +/// ActionScript Object parameter to the handler. +/// +/// The XMP is placed in a script data tag with the name "onXMPData". A variety of legacy metadata +/// is contained in a script data tag with the name "onMetaData". This contains only "internal" +/// information (like duration or width/height), nothing that is user or author editiable (like +/// title or description). Some of these legacy items are imported into the XMP, none are updated +/// from the XMP. +/// +/// A script data tag's content is: +/// +/// UI8 0x02 +/// UI16 name length - includes nul terminator if present +/// UI8n object name - UTF-8, possibly with nul terminator +/// ... object value - serialized ActionScript value (SCRIPTDATAVALUE in FLV spec) +/// +/// The onXMPData and onMetaData values are both ECMA arrays. These have more in common with XMP +/// structs than arrays, the items have arbitrary string names. The serialized form is: +/// +/// UI8 0x08 +/// UI32 array length - need not be exact, an optimization hint +/// array items +/// UI16 name length - includes nul terminator if present +/// UI8n item name - UTF-8, possibly with nul terminator +/// ... object value - serialized ActionScript value (SCRIPTDATAVALUE in FLV spec) +/// UI24 0x000009 - array terminator +/// +/// The object names and array item names in sample files do not have a nul terminator. The policy +/// here is to treat them as optional when reading, and to omit them when writing. +/// +/// The onXMPData array typically has one item named "liveXML". The value of this is a short or long +/// string as necessary: +/// +/// UI8 type - 2 for a short string, 12 for a long string +/// UIx value length - UI16 for a short string, UI32 for a long string, includes nul terminator +/// UI8n value - UTF-8 with nul terminator +/// +// ================================================================================================= + +static inline XMP_Uns32 GetUns24BE ( const void * addr ) +{ + return (GetUns32BE(addr) >> 8); +} + +static inline void PutUns24BE ( XMP_Uns32 value, void * addr ) +{ + XMP_Uns8 * bytes = (XMP_Uns8*)addr; + bytes[0] = (XMP_Uns8)(value >> 16); + bytes[1] = (XMP_Uns8)(value >> 8); + bytes[2] = (XMP_Uns8)(value); +} + +// ================================================================================================= +// FLV_CheckFormat +// =============== +// +// Check for "FLV" and 1 in the first 4 bytes, that the header length is at least 9, that the file +// size is at least as big as the header, and that the leading 0 back pointer is present if the file +// is bigger than the header. + +#define kFLV1 0x464C5601UL + +bool FLV_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ) +{ + XMP_Uns8 buffer [9]; + + LFA_Seek ( fileRef, 0, SEEK_SET ); + XMP_Uns32 ioCount = LFA_Read ( fileRef, buffer, 9 ); + if ( ioCount != 9 ) return false; + + XMP_Uns32 fileSignature = GetUns32BE ( &buffer[0] ); + if ( fileSignature != kFLV1 ) return false; + + XMP_Uns32 headerSize = GetUns32BE ( &buffer[5] ); + XMP_Uns64 fileSize = LFA_Measure ( fileRef ); + if ( (fileSize < (headerSize + 4)) && (fileSize != headerSize) ) return false; + + if ( fileSize >= (headerSize + 4) ) { + XMP_Uns32 bpZero; + LFA_Seek ( fileRef, headerSize, SEEK_SET ); + ioCount = LFA_Read ( fileRef, &bpZero, 4 ); + if ( (ioCount != 4) || (bpZero != 0) ) return false; + } + + return true; + +} // FLV_CheckFormat + +// ================================================================================================= +// FLV_MetaHandlerCTor +// =================== + +XMPFileHandler * FLV_MetaHandlerCTor ( XMPFiles * parent ) +{ + + return new FLV_MetaHandler ( parent ); + +} // FLV_MetaHandlerCTor + +// ================================================================================================= +// FLV_MetaHandler::FLV_MetaHandler +// ================================ + +FLV_MetaHandler::FLV_MetaHandler ( XMPFiles * _parent ) + : flvHeaderLen(0), longXMP(false), xmpTagPos(0), omdTagPos(0), xmpTagLen(0), omdTagLen(0) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kFLV_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} // FLV_MetaHandler::FLV_MetaHandler + +// ================================================================================================= +// FLV_MetaHandler::~FLV_MetaHandler +// ================================= + +FLV_MetaHandler::~FLV_MetaHandler() +{ + + // Nothing to do yet. + +} // FLV_MetaHandler::~FLV_MetaHandler + +// ================================================================================================= +// GetTagInfo +// ========== +// +// Seek to the start of a tag and extract the type, data size, and timestamp. Leave the file +// positioned at the first byte of data. + +struct TagInfo { + XMP_Uns8 type; + XMP_Uns32 time; + XMP_Uns32 dataSize; +}; + +static void GetTagInfo ( LFA_FileRef fileRef, XMP_Uns64 tagPos, TagInfo * info ) +{ + XMP_Uns8 buffer [11]; + + LFA_Seek ( fileRef, tagPos, SEEK_SET ); + LFA_Read ( fileRef, buffer, 11, kLFA_RequireAll ); + + info->type = buffer[0]; + info->time = GetUns24BE ( &buffer[4] ) || (buffer[7] << 24); + info->dataSize = GetUns24BE ( &buffer[1] ); + +} // GetTagInfo + +// ================================================================================================= +// GetASValueLen +// ============= +// +// Return the full length of a serialized ActionScript value, including the type byte, zero if unknown. + +static XMP_Uns32 GetASValueLen ( const XMP_Uns8 * asValue, const XMP_Uns8 * asLimit ) +{ + XMP_Uns32 valueLen = 0; + const XMP_Uns8 * itemPtr; + XMP_Uns32 arrayCount; + + switch ( asValue[0] ) { + + case 0 : // IEEE double + valueLen = 1 + 8; + break; + + case 1 : // UI8 Boolean + valueLen = 1 + 1; + break; + + case 2 : // Short string + valueLen = 1 + 2 + GetUns16BE ( &asValue[1] ); + break; + + case 3 : // ActionScript object, a name and value. + itemPtr = &asValue[1]; + itemPtr += 2 + GetUns16BE ( itemPtr ); // Move past the name portion. + itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion. + valueLen = (XMP_Uns32) (itemPtr - asValue); + break; + + case 4 : // Short string (movie clip path) + valueLen = 1 + 2 + GetUns16BE ( &asValue[1] ); + break; + + case 5 : // Null + valueLen = 1; + break; + + case 6 : // Undefined + valueLen = 1; + break; + + case 7 : // UI16 reference ID + valueLen = 1 + 2; + break; + + case 8 : // ECMA array, ignore the count, look for the 0x000009 terminator. + itemPtr = &asValue[5]; + while ( itemPtr < asLimit ) { + XMP_Uns16 nameLen = GetUns16BE ( itemPtr ); + itemPtr += 2 + nameLen; // Move past the name portion. + if ( (nameLen == 0) && (*itemPtr == 9) ) { + itemPtr += 1; + break; // Done, found the 0x000009 terminator. + } + itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion. + } + valueLen = (XMP_Uns32) (itemPtr - asValue); + break; + + case 10 : // Strict array, has an exact count. + arrayCount = GetUns32BE ( &asValue[1] ); + itemPtr = &asValue[5]; + for ( ; (arrayCount > 0) && (itemPtr < asLimit); --arrayCount ) { + itemPtr += 2 + GetUns16BE ( itemPtr ); // Move past the name portion. + itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion. + } + valueLen = (XMP_Uns32) (itemPtr - asValue); + break; + + case 11 : // Date + valueLen = 1 + 8 + 2; + break; + + case 12: // Long string + valueLen = 1 + 4 + GetUns32BE ( &asValue[1] ); + break; + + } + + return valueLen; + +} // GetASValueLen + +// ================================================================================================= +// CheckName +// ========= +// +// Check for the name portion of a script data tag or array item, with optional nul terminator. The +// wantedLen must not count the terminator. + +static inline bool CheckName ( XMP_StringPtr inputName, XMP_Uns16 inputLen, + XMP_StringPtr wantedName, XMP_Uns16 wantedLen ) +{ + + if ( inputLen == wantedLen+1 ) { + if ( inputName[wantedLen] != 0 ) return false; // Extra byte must be terminating nul. + --inputLen; + } + + if ( (inputLen == wantedLen) && XMP_LitNMatch ( inputName, wantedName, wantedLen ) ) return true; + return false; + +} // CheckName + +// ================================================================================================= +// FLV_MetaHandler::CacheFileData +// ============================== +// +// Look for the onXMPData and onMetaData script data tags at time 0. Cache all of onMetaData, it +// shouldn't be that big and this removes a need to know what is reconciled. It can't be more than +// 16MB anyway, the size field is only 24 bits. + +void FLV_MetaHandler::CacheFileData() +{ + XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + + 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_Uns8 buffer [16]; // Enough for 1+2+"onMetaData"+nul. + XMP_Uns32 ioCount; + TagInfo info; + + LFA_Seek ( fileRef, 5, SEEK_SET ); + LFA_Read ( fileRef, buffer, 4, kLFA_RequireAll ); + + this->flvHeaderLen = GetUns32BE ( &buffer[0] ); + XMP_Uns32 firstTagPos = this->flvHeaderLen + 4; // Include the initial zero back pointer. + + if ( firstTagPos >= fileSize ) return; // Quit now if the file is just a header. + + for ( XMP_Uns64 tagPos = firstTagPos; tagPos < fileSize; tagPos += (11 + info.dataSize + 4) ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "FLV_MetaHandler::LookForMetadata - User abort", kXMPErr_UserAbort ); + } + + GetTagInfo ( fileRef, tagPos, &info ); // ! GetTagInfo seeks to the tag offset. + if ( info.time != 0 ) break; + if ( info.type != 18 ) continue; + + XMP_Assert ( sizeof(buffer) >= (1+2+10+1) ); // 02 000B onMetaData 00 + ioCount = LFA_Read ( fileRef, buffer, sizeof(buffer) ); + if ( (ioCount < 4) || (buffer[0] != 0x02) ) continue; + + XMP_Uns16 nameLen = GetUns16BE ( &buffer[1] ); + XMP_StringPtr namePtr = (XMP_StringPtr)(&buffer[3]); + + if ( this->onXMP.empty() && CheckName ( namePtr, nameLen, "onXMPData", 9 ) ) { + + // ! Put the raw data in onXMPData, analyze the value in ProcessXMP. + + this->xmpTagPos = tagPos; + this->xmpTagLen = 11 + info.dataSize + 4; // ! Includes the trailing back pointer. + + this->packetInfo.offset = tagPos + 11 + 1+2+nameLen; // ! Not the real offset yet, the offset of the onXMPData value. + + ioCount = info.dataSize - (1+2+nameLen); // Just the onXMPData value portion. + this->onXMP.reserve ( ioCount ); + this->onXMP.assign ( ioCount, ' ' ); + LFA_Seek ( fileRef, this->packetInfo.offset, SEEK_SET ); + LFA_Read ( fileRef, (void*)this->onXMP.data(), ioCount, kLFA_RequireAll ); + + if ( ! this->onMetaData.empty() ) break; // Done if we've found both. + + } else if ( this->onMetaData.empty() && CheckName ( namePtr, nameLen, "onMetaData", 10 ) ) { + + this->omdTagPos = tagPos; + this->omdTagLen = 11 + info.dataSize + 4; // ! Includes the trailing back pointer. + + ioCount = info.dataSize - (1+2+nameLen); // Just the onMetaData value portion. + this->onMetaData.reserve ( ioCount ); + this->onMetaData.assign ( ioCount, ' ' ); + LFA_Seek ( fileRef, (tagPos + 11 + 1+2+nameLen), SEEK_SET ); + LFA_Read ( fileRef, (void*)this->onMetaData.data(), ioCount, kLFA_RequireAll ); + + if ( ! this->onXMP.empty() ) break; // Done if we've found both. + + } + + } + +} // FLV_MetaHandler::CacheFileData + +// ================================================================================================= +// FLV_MetaHandler::MakeLegacyDigest +// ================================= + +#define kHexDigits "0123456789ABCDEF" + +void FLV_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + MD5_CTX context; + unsigned char digestBin [16]; + + MD5Init ( &context ); + MD5Update ( &context, (XMP_Uns8*)this->onMetaData.data(), (unsigned int)this->onMetaData.size() ); + MD5Final ( digestBin, &context ); + + char buffer [40]; + for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digestBin[in]; + buffer[out] = kHexDigits [ byte >> 4 ]; + buffer[out+1] = kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + digestStr->erase(); + digestStr->append ( buffer, 32 ); + +} // FLV_MetaHandler::MakeLegacyDigest + +// ================================================================================================= +// FLV_MetaHandler::ExtractLiveXML +// =============================== +// +// Extract the XMP packet from the cached onXMPData ECMA array's "liveXMP" item. + +void FLV_MetaHandler::ExtractLiveXML() +{ + if ( this->onXMP[0] != 0x08 ) return; // Make sure onXMPData is an ECMA array. + const XMP_Uns8 * ecmaArray = (const XMP_Uns8 *) this->onXMP.c_str(); + const XMP_Uns8 * ecmaLimit = ecmaArray + this->onXMP.size(); + + if ( this->onXMP.size() >= 3 ) { // Omit the 0x000009 terminator, simplifies the loop. + if ( GetUns24BE ( ecmaLimit-3 ) == 9 ) ecmaLimit -= 3; + } + + for ( const XMP_Uns8 * itemPtr = ecmaArray + 5; itemPtr < ecmaLimit; /* internal increment */ ) { + + // Find the "liveXML" array item, make sure it is a short or long string. + + XMP_Uns16 nameLen = GetUns16BE ( itemPtr ); + const XMP_Uns8 * namePtr = itemPtr + 2; + + itemPtr += (2 + nameLen); // Move to the value portion. + XMP_Uns32 valueLen = GetASValueLen ( itemPtr, ecmaLimit ); + if ( valueLen == 0 ) return; // ! Unknown value type, can't look further. + + if ( CheckName ( (char*)namePtr, nameLen, "liveXML", 7 ) ) { + + XMP_Uns32 lenLen = 2; // Assume a short string. + if ( *itemPtr == 12 ) { + lenLen = 4; + this->longXMP = true; + } else if ( *itemPtr != 2 ) { + return; // Not a short or long string. + } + + valueLen -= (1 + lenLen); + itemPtr += (1 + lenLen); + + this->packetInfo.offset += (itemPtr - ecmaArray); + this->packetInfo.length += valueLen; + + this->xmpPacket.reserve ( valueLen ); + this->xmpPacket.assign ( (char*)itemPtr, valueLen ); + + return; + + } + + itemPtr += valueLen; // Move past the value portion. + + } + +} // FLV_MetaHandler::ExtractLiveXML + +// ================================================================================================= +// FLV_MetaHandler::ProcessXMP +// =========================== + +void FLV_MetaHandler::ProcessXMP() +{ + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( ! this->onXMP.empty() ) { // Look for the XMP packet. + + this->ExtractLiveXML(); + if ( ! this->xmpPacket.empty() ) { + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + this->containsXMP = true; + } + + } + + // Now process the legacy, if necessary. + + if ( this->onMetaData.empty() ) return; // No legacy, we're done. + + std::string oldDigest; + bool oldDigestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "FLV", &oldDigest, 0 ); + + if ( oldDigestFound ) { + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + if ( oldDigest == newDigest ) return; // No legacy changes. + } + + // *** No spec yet for what legacy to reconcile. + +} // FLV_MetaHandler::ProcessXMP + +// ================================================================================================= +// FLV_MetaHandler::UpdateFile +// =========================== + +void FLV_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + LFA_FileRef fileRef = this->parent->fileRef; + XMP_Uns64 fileSize = LFA_Measure ( fileRef ); + + // Make sure the XMP has a legacy digest if appropriate. + + if ( ! this->onMetaData.empty() ) { + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", + kXMP_NS_XMP, "FLV", newDigest.c_str(), kXMP_DeleteExisting ); + + try { + XMP_StringLen xmpLen = (XMP_StringLen)this->xmpPacket.size(); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength), xmpLen ); + } catch ( ... ) { + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + } + + // Rewrite the packet in-place if it fits. Otherwise rewrite the whole file. + + if ( this->xmpPacket.size() == (size_t)this->packetInfo.length ) { + + LFA_Seek ( fileRef, this->packetInfo.offset, SEEK_SET ); + LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() ); + + } else { + + std::string origPath = this->parent->filePath; + LFA_FileRef origRef = this->parent->fileRef; + + std::string updatePath; + LFA_FileRef updateRef = 0; + + CreateTempFile ( origPath, &updatePath ); + updateRef = LFA_Open ( updatePath.c_str(), 'w' ); + + this->parent->filePath = updatePath; + this->parent->fileRef = updateRef; + + try { + this->WriteFile ( origRef, origPath ); + } catch ( ... ) { + LFA_Close ( updateRef ); + 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; + +} // FLV_MetaHandler::UpdateFile + +// ================================================================================================= +// WriteOnXMP +// ========== +// +// Write the XMP packet wrapped up in an ECMA array script data tag: +// +// 0 UI8 tag type : 18 +// 1 UI24 content length : 1+2+9+1+4+2+7+1 + <2 or 4> + XMP packet size + 1 + 3 +// 4 UI24 time low : 0 +// 7 UI8 time high : 0 +// 8 UI24 stream ID : 0 +// +// 11 UI8 0x02 +// 12 UI16 name length : 9 +// 14 str9 tag name : "onXMPData", no nul terminator +// 23 UI8 value type : 8 +// 24 UI32 array count : 1 +// 28 UI16 name length : 7 +// 30 str7 item name : "liveXML", no nul terminator +// +// 37 UI8 value type : 2 for a short string, 12 for a long string +// 38 UIn XMP packet size + 1, UI16 or UI32 as needed +// -- str XMP packet, with nul terminator +// +// -- UI24 array terminator : 0x000009 +// -- UI32 back pointer : content length + 11 + +static void WriteOnXMP ( LFA_FileRef fileRef, const std::string & xmpPacket ) +{ + char buffer [64]; + bool longXMP = false; + XMP_Uns32 tagLen = 1+2+9+1+4+2+7+1 + 2 + (XMP_Uns32)xmpPacket.size() + 1 + 3; + + if ( xmpPacket.size() > 0xFFFE ) { + longXMP = true; + tagLen += 2; + } + + if ( tagLen > 16*1024*1024 ) XMP_Throw ( "FLV tags can't be larger than 16MB", kXMPErr_TBD ); + + // Fill in the script data tag header. + + buffer[0] = 18; + PutUns24BE ( tagLen, &buffer[1] ); + PutUns24BE ( 0, &buffer[4] ); + buffer[7] = 0; + PutUns24BE ( 0, &buffer[8] ); + + // Fill in the "onXMPData" name, ECMA array start, and "liveXML" name. + + buffer[11] = 2; + PutUns16BE ( 9, &buffer[12] ); + memcpy ( &buffer[14], "onXMPData", 9 ); // AUDIT: Safe, buffer has 64 chars. + buffer[23] = 8; + PutUns32BE ( 1, &buffer[24] ); + PutUns16BE ( 7, &buffer[28] ); + memcpy ( &buffer[30], "liveXML", 7 ); // AUDIT: Safe, buffer has 64 chars. + + // Fill in the XMP packet string type and length, write what we have so far. + + LFA_Seek ( fileRef, 0, SEEK_END ); + if ( ! longXMP ) { + buffer[37] = 2; + PutUns16BE ( (XMP_Uns16)xmpPacket.size()+1, &buffer[38] ); + LFA_Write ( fileRef, buffer, 40 ); + } else { + buffer[37] = 12; + PutUns32BE ( (XMP_Uns32)xmpPacket.size()+1, &buffer[38] ); + LFA_Write ( fileRef, buffer, 42 ); + } + + // Write the XMP packet, nul terminator, array terminator, and back pointer. + + LFA_Write ( fileRef, xmpPacket.c_str(), (XMP_Int32)xmpPacket.size()+1 ); + PutUns24BE ( 9, &buffer[0] ); + PutUns32BE ( tagLen+11, &buffer[3] ); + LFA_Write ( fileRef, buffer, 7 ); + +} // WriteOnXMP + +// ================================================================================================= +// FLV_MetaHandler::WriteFile +// ========================== +// +// Use a source (old) file and the current XMP to build a destination (new) file. All of the source +// file is copied except for previous XMP. The current XMP is inserted after onMetaData, or at least +// before the first time 0 audio or video tag. + +// ! We do not currently update anything in onMetaData. + +void FLV_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) +{ + if ( ! this->needsUpdate ) return; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + LFA_FileRef destRef = this->parent->fileRef; + + XMP_Uns64 sourceLen = LFA_Measure ( sourceRef ); + XMP_Uns64 sourcePos = 0; + + LFA_Seek ( sourceRef, 0, SEEK_SET ); + LFA_Seek ( destRef, 0, SEEK_SET ); + LFA_Truncate ( destRef, 0 ); + + // First do whatever is needed to put the new XMP after any existing onMetaData tag, or as the + // first time 0 tag. + + if ( this->omdTagPos == 0 ) { + + // There is no onMetaData tag. Copy the file header, then write the new XMP as the first tag. + // Allow the degenerate case of a file with just a header, no initial back pointer or tags. + + LFA_Copy ( sourceRef, destRef, this->flvHeaderLen, abortProc, abortArg ); + + XMP_Uns32 zero = 0; // Don't require the initial 0 back pointer to be in the source file. + LFA_Write ( destRef, &zero, 4 ); + + sourcePos = this->flvHeaderLen + 4; + + WriteOnXMP ( destRef, this->xmpPacket ); + + } else { + + // There is an onMetaData tag. Copy the front of the file through the onMetaData tag, + // skipping any XMP that happens to be in the way. The XMP should not be before onMetaData, + // but let's be robust. Write the new XMP immediately after onMetaData, at the same time. + + XMP_Uns64 omdEnd = this->omdTagPos + this->omdTagLen; + + if ( (this->xmpTagPos != 0) && (this->xmpTagPos < this->omdTagPos) ) { + LFA_Copy ( sourceRef, destRef, this->xmpTagPos, abortProc, abortArg ); + sourcePos = this->xmpTagPos + this->xmpTagLen; + LFA_Seek ( sourceRef, sourcePos, SEEK_SET ); + } + + LFA_Copy ( sourceRef, destRef, (omdEnd - sourcePos), abortProc, abortArg ); + sourcePos = omdEnd; + + WriteOnXMP ( destRef, this->xmpPacket ); + + } + + // Copy the rest of the file, skipping any XMP that is in the way. + + if ( (this->xmpTagPos != 0) && (this->xmpTagPos >= sourcePos) ) { + LFA_Copy ( sourceRef, destRef, (this->xmpTagPos - sourcePos), abortProc, abortArg ); + sourcePos = this->xmpTagPos + this->xmpTagLen; + LFA_Seek ( sourceRef, sourcePos, SEEK_SET ); + } + + LFA_Copy ( sourceRef, destRef, (sourceLen - sourcePos), abortProc, abortArg ); + + this->needsUpdate = false; + +} // FLV_MetaHandler::WriteFile + +// ================================================================================================= diff --git a/source/XMPFiles/FileHandlers/FLV_Handler.hpp b/source/XMPFiles/FileHandlers/FLV_Handler.hpp new file mode 100644 index 0000000..1fea76d --- /dev/null +++ b/source/XMPFiles/FileHandlers/FLV_Handler.hpp @@ -0,0 +1,73 @@ +#ifndef __FLV_Handler_hpp__ +#define __FLV_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 FLV_Handler.hpp +/// \brief File format handler for FLV. +/// +/// This header ... +/// +// ================================================================================================ + +extern XMPFileHandler * FLV_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool FLV_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kFLV_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate + ); + +class FLV_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); + + FLV_MetaHandler ( XMPFiles * _parent ); + virtual ~FLV_MetaHandler(); + +private: + + FLV_MetaHandler() : flvHeaderLen(0), longXMP(false), + xmpTagPos(0), omdTagPos(0), xmpTagLen(0), omdTagLen(0) {}; // Hidden on purpose. + + void ExtractLiveXML(); + void MakeLegacyDigest ( std::string * digestStr ); + + XMP_Uns32 flvHeaderLen; + bool longXMP; // True if the stored XMP is a long string (4 byte length). + + XMP_Uns64 xmpTagPos, omdTagPos; // The file offset and length of onXMP and onMetaData tags. + XMP_Uns32 xmpTagLen, omdTagLen; // Zero if the tag is not present. + + std::string onXMP, onMetaData; // ! Actually contains structured binary data. + +}; // FLV_MetaHandler + +// ================================================================================================= + +#endif // __FLV_Handler_hpp__ diff --git a/source/XMPFiles/FileHandlers/InDesign_Handler.cpp b/source/XMPFiles/FileHandlers/InDesign_Handler.cpp index 55332c0..c26db20 100644 --- a/source/XMPFiles/FileHandlers/InDesign_Handler.cpp +++ b/source/XMPFiles/FileHandlers/InDesign_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 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 @@ -208,14 +208,15 @@ void InDesign_MetaHandler::CacheFileData() XMP_Assert ( ! this->streamBigEndian ); if ( cobjEndian == kINDD_BigEndian ) this->streamBigEndian = true; - + // --------------------------------------------------------------------------------------------- // Look for the XMP contiguous object stream. Most of the time there will be just one stream and // it will be the XMP. So we might as well fill the whole buffer and not worry about reading too // much and seeking back to the start of the following stream. XMP_Int64 cobjPos = (XMP_Int64)dbPages * kINDD_PageSize; // ! Use a 64 bit multiply! - XMP_Uns32 streamLength = (XMP_Uns32)(-(long)(2*sizeof(InDesignContigObjMarker))); // ! For the first pass in the loop. + cobjPos -= (2 * sizeof(InDesignContigObjMarker)); // ! For the first pass in the loop. + XMP_Uns32 streamLength = 0; // ! For the first pass in the loop. while ( true ) { @@ -225,7 +226,7 @@ void InDesign_MetaHandler::CacheFileData() // Fetch the start of the next stream and check the contiguous object header. // ! The writeable bit of fObjectClassID is ignored, we use the packet trailer flag. - + cobjPos += streamLength + (2 * sizeof(InDesignContigObjMarker)); ioBuf.filePos = cobjPos; ioBuf.ptr = ioBuf.limit; // Make sure RefillBuffer does a simple read. @@ -331,18 +332,18 @@ void InDesign_MetaHandler::WriteXMPPrefix() // Write the contiguous object header and the 4 byte length of the XMP packet. LFA_FileRef fileRef = this->parent->fileRef; - XMP_PacketInfo & packetInfo = this->packetInfo; + XMP_Uns32 packetSize = (XMP_Uns32)this->xmpPacket.size(); InDesignContigObjMarker header; memcpy ( header.fGUID, kINDDContigObjHeaderGUID, sizeof(header.fGUID) ); // AUDIT: Use of dest sizeof for length is safe. header.fObjectUID = this->xmpObjID; header.fObjectClassID = this->xmpClassID; - header.fStreamLength = MakeUns32LE ( 4 + packetInfo.length ); + header.fStreamLength = MakeUns32LE ( 4 + packetSize ); header.fChecksum = (XMP_Uns32)(-1); LFA_Write ( fileRef, &header, sizeof(header) ); - XMP_Uns32 pktLength = MakeUns32LE ( packetInfo.length ); - if ( this->streamBigEndian ) pktLength = MakeUns32BE ( packetInfo.length ); + XMP_Uns32 pktLength = MakeUns32LE ( packetSize ); + if ( this->streamBigEndian ) pktLength = MakeUns32BE ( packetSize ); LFA_Write ( fileRef, &pktLength, sizeof(pktLength) ); } // InDesign_MetaHandler::WriteXMPPrefix @@ -356,14 +357,14 @@ void InDesign_MetaHandler::WriteXMPSuffix() // Write the contiguous object trailer. LFA_FileRef fileRef = this->parent->fileRef; - XMP_PacketInfo & packetInfo = this->packetInfo; + XMP_Uns32 packetSize = (XMP_Uns32)this->xmpPacket.size(); InDesignContigObjMarker trailer; memcpy ( trailer.fGUID, kINDDContigObjTrailerGUID, sizeof(trailer.fGUID) ); // AUDIT: Use of dest sizeof for length is safe. trailer.fObjectUID = this->xmpObjID; trailer.fObjectClassID = this->xmpClassID; - trailer.fStreamLength = MakeUns32LE ( 4 + packetInfo.length ); + trailer.fStreamLength = MakeUns32LE ( 4 + packetSize ); trailer.fChecksum = (XMP_Uns32)(-1); LFA_Write ( fileRef, &trailer, sizeof(trailer) ); @@ -409,7 +410,6 @@ void InDesign_MetaHandler::RestoreFileEnding() // Pad the file with zeros to a page boundary. LFA_FileRef fileRef = this->parent->fileRef; - XMP_PacketInfo & packetInfo = this->packetInfo; XMP_Int64 dataLength = LFA_Measure ( fileRef ); XMP_Int32 padLength = (kINDD_PageSize - ((XMP_Int32)dataLength & kINDD_PageMask)) & kINDD_PageMask; diff --git a/source/XMPFiles/FileHandlers/JPEG_Handler.cpp b/source/XMPFiles/FileHandlers/JPEG_Handler.cpp index e57c83f..6917355 100644 --- a/source/XMPFiles/FileHandlers/JPEG_Handler.cpp +++ b/source/XMPFiles/FileHandlers/JPEG_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 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 @@ -352,7 +352,7 @@ void JPEG_MetaHandler::CacheFileData() if ( ! ok ) return; // Must be a truncated file. this->packetInfo.offset = ioBuf.filePos + (ioBuf.ptr - &ioBuf.data[0]); - this->packetInfo.length = segLen; + this->packetInfo.length = (XMP_Int32)segLen; this->packetInfo.padSize = 0; // Assume for now, set these properly in ProcessXMP. this->packetInfo.charForm = kXMP_CharUnknown; this->packetInfo.writeable = true; @@ -544,7 +544,7 @@ void JPEG_MetaHandler::ProcessTNail() } else { this->exifMgr = new TIFF_FileWriter(); } - this->exifMgr->ParseMemoryStream ( this->exifContents.c_str(), this->exifContents.size() ); + this->exifMgr->ParseMemoryStream ( this->exifContents.c_str(), (XMP_Uns32)this->exifContents.size() ); } this->containsTNail = this->exifMgr->GetTNailInfo ( &this->tnailInfo ); @@ -577,7 +577,12 @@ void JPEG_MetaHandler::ProcessXMP() } else { if ( this->exifMgr == 0 ) this->exifMgr = new TIFF_FileWriter(); this->psirMgr = new PSIR_FileWriter(); - this->iptcMgr = new IPTC_Writer(); // ! Parse it later. + #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 } // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy @@ -594,11 +599,11 @@ void JPEG_MetaHandler::ProcessXMP() IPTC_Manager & iptc = *this->iptcMgr; if ( haveExif ) { - exif.ParseMemoryStream ( this->exifContents.c_str(), this->exifContents.size() ); + exif.ParseMemoryStream ( this->exifContents.c_str(), (XMP_Uns32)this->exifContents.size() ); } if ( ! this->psirContents.empty() ) { - psir.ParseMemoryResources ( this->psirContents.c_str(), this->psirContents.size() ); + 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 @@ -643,7 +648,7 @@ void JPEG_MetaHandler::ProcessXMP() XMP_Assert ( this->containsXMP ); // Common code takes care of packetInfo.charForm, .padSize, and .writeable. XMP_StringPtr packetStr = this->xmpPacket.c_str(); - XMP_StringLen packetLen = this->xmpPacket.size(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); try { this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); } catch ( ... ) { @@ -677,7 +682,7 @@ void JPEG_MetaHandler::ProcessXMP() if ( guidPos != this->extendedXMP.end() ) { try { XMP_StringPtr extStr = guidPos->second.c_str(); - XMP_StringLen extLen = guidPos->second.size(); + XMP_StringLen extLen = (XMP_StringLen)guidPos->second.size(); SXMPMeta extXMP ( extStr, extLen ); SXMPUtils::MergeFromJPEG ( &this->xmpObj, extXMP ); } catch ( ... ) { @@ -716,18 +721,24 @@ void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; - bool doInPlace = (oldPacketOffset != 0) && (oldPacketLength != 0); // ! Has old packet and new packet fits. + bool doInPlace = (this->xmpPacket.size() <= (size_t)this->packetInfo.length); - if ( doInPlace && (! this->extendedXMP.empty()) ) doInPlace = false; + if ( ! this->extendedXMP.empty() ) doInPlace = false; - if ( doInPlace && (this->exifMgr != 0) && (this->exifMgr->IsLegacyChanged()) ) doInPlace = false; - if ( doInPlace && (this->psirMgr != 0) && (this->psirMgr->IsLegacyChanged()) ) doInPlace = false; + if ( (this->exifMgr != 0) && (this->exifMgr->IsLegacyChanged()) ) doInPlace = false; + if ( (this->psirMgr != 0) && (this->psirMgr->IsLegacyChanged()) ) doInPlace = false; if ( doInPlace ) { #if GatherPerformanceData sAPIPerf->back().extraInfo += ", JPEG in-place update"; #endif + + if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { + // They ought to match, cheap to be sure. + size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); + this->xmpPacket.append ( extraSpace, ' ' ); + } LFA_FileRef liveFile = this->parent->fileRef; std::string & newPacket = this->xmpPacket; @@ -735,7 +746,7 @@ void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) XMP_Assert ( newPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic. LFA_Seek ( liveFile, oldPacketOffset, SEEK_SET ); - LFA_Write ( liveFile, newPacket.c_str(), newPacket.size() ); + LFA_Write ( liveFile, newPacket.c_str(), (XMP_Int32)newPacket.size() ); } else { @@ -851,7 +862,7 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so 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, segLen ); + LFA_Write ( destRef, ioBuf.ptr, (XMP_Int32)segLen ); ioBuf.ptr += segLen; } @@ -880,10 +891,10 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so SXMPUtils::PackageForJPEG ( this->xmpObj, &mainXMP, &extXMP, &extDigest ); XMP_Assert ( (extXMP.size() == 0) || (extDigest.size() == 32) ); - first4 = MakeUns32BE ( 0xFFE10000 + 2 + kMainXMPSignatureLength + mainXMP.size() ); + 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(), mainXMP.size() ); + LFA_Write ( destRef, mainXMP.c_str(), (XMP_Int32)mainXMP.size() ); size_t extPos = 0; size_t extLen = extXMP.size(); @@ -893,18 +904,18 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so size_t partLen = extLen; if ( partLen > 65000 ) partLen = 65000; - first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExtXMPPrefixLength + partLen ); + first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExtXMPPrefixLength + (XMP_Uns32)partLen ); LFA_Write ( destRef, &first4, 4 ); LFA_Write ( destRef, kExtXMPSignatureString, kExtXMPSignatureLength ); - LFA_Write ( destRef, extDigest.c_str(), extDigest.size() ); + LFA_Write ( destRef, extDigest.c_str(), (XMP_Int32)extDigest.size() ); - first4 = MakeUns32BE ( extXMP.size() ); + first4 = MakeUns32BE ( (XMP_Int32)extXMP.size() ); LFA_Write ( destRef, &first4, 4 ); - first4 = MakeUns32BE ( extPos ); + first4 = MakeUns32BE ( (XMP_Int32)extPos ); LFA_Write ( destRef, &first4, 4 ); - LFA_Write ( destRef, &extXMP[extPos], partLen ); + LFA_Write ( destRef, &extXMP[extPos], (XMP_Int32)partLen ); extPos += partLen; extLen -= partLen; @@ -973,7 +984,7 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so } } - if ( copySegment ) LFA_Write ( destRef, ioBuf.ptr, 2+segLen ); + if ( copySegment ) LFA_Write ( destRef, ioBuf.ptr, (XMP_Int32)(2+segLen) ); ioBuf.ptr += 2+segLen; @@ -982,13 +993,13 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so // Copy the remainder of the source file. size_t bufTail = ioBuf.len - (ioBuf.ptr - &ioBuf.data[0]); - LFA_Write ( destRef, ioBuf.ptr, bufTail ); + 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, ioBuf.len ); + LFA_Write ( destRef, ioBuf.ptr, (XMP_Int32)ioBuf.len ); ioBuf.ptr += ioBuf.len; } diff --git a/source/XMPFiles/FileHandlers/MOV_Handler.cpp b/source/XMPFiles/FileHandlers/MOV_Handler.cpp index d7b6dcd..e2ea26c 100644 --- a/source/XMPFiles/FileHandlers/MOV_Handler.cpp +++ b/source/XMPFiles/FileHandlers/MOV_Handler.cpp @@ -1,13 +1,16 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 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. // ================================================================================================= -#if WIN_ENV +#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 @@ -30,6 +33,14 @@ 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. @@ -48,7 +59,6 @@ XMPFileHandler * MOV_MetaHandlerCTor ( XMPFiles * parent ) } // MOV_MetaHandlerCTor - // ================================================================================================= // MOV_CheckFormat // =============== @@ -108,7 +118,7 @@ EXIT: // ================================ MOV_MetaHandler::MOV_MetaHandler ( XMPFiles * _parent ) - : mQTInit(false), mMovieDataRef(0), mMovieDataHandler(0), mMovie(NULL), mMovieResourceID(0), mFilePermission(0) + : mQTInit(false), mMovieDataRef(0), mMovieDataHandler(0), mMovie(0), mMovieResourceID(0), mFilePermission(0) { this->parent = _parent; @@ -131,6 +141,26 @@ MOV_MetaHandler::~MOV_MetaHandler() } // 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 // =========================== @@ -140,7 +170,7 @@ void MOV_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( doSafeUpdate ) XMP_Throw ( "MOV_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); XMP_StringPtr packetStr = this->xmpPacket.c_str(); - XMP_StringLen packetLen = this->xmpPacket.size(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); if ( packetLen == 0 ) return; // Bail if no XMP packet @@ -152,7 +182,7 @@ void MOV_MetaHandler::UpdateFile ( bool doSafeUpdate ) OSErr err; // Remove previous versions - err = GetUserData ( movieUserData, NULL, kXMPUserDataType, kXMPUserDataTypeIndex ); + err = GetUserData ( movieUserData, 0, kXMPUserDataType, kXMPUserDataTypeIndex ); if ( err == noErr ) { RemoveUserData(movieUserData, kXMPUserDataType, kXMPUserDataTypeIndex); } @@ -167,6 +197,8 @@ void MOV_MetaHandler::UpdateFile ( bool doSafeUpdate ) DisposeHandle ( XMPdata ); } + CreatorAtom_Update ( this->xmpObj, movieUserData ); + } } @@ -263,12 +295,230 @@ void MOV_MetaHandler::CloseMovie() } // 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 ) ) { @@ -297,6 +547,8 @@ void MOV_MetaHandler::CacheFileData() this->packetInfo.length = (XMP_Int32)dataSize; this->containsXMP = true; + CreatorAtom_ReadStrings ( mCreatorAtomStrings, movieUserData ); + } DisposeHandle ( XMPdataHandle ); @@ -310,3 +562,435 @@ void MOV_MetaHandler::CacheFileData() } // 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 index 9c7f8c3..4d398ce 100644 --- a/source/XMPFiles/FileHandlers/MOV_Handler.hpp +++ b/source/XMPFiles/FileHandlers/MOV_Handler.hpp @@ -3,13 +3,16 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 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 @@ -53,10 +56,24 @@ public: ~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; @@ -73,4 +90,5 @@ protected: // ================================================================================================= +#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 56dcf09..f906900 100644 --- a/source/XMPFiles/FileHandlers/MP3_Handler.cpp +++ b/source/XMPFiles/FileHandlers/MP3_Handler.cpp @@ -7,6 +7,9 @@ // 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 "MP3_Handler.hpp" #include "ID3_Support.hpp" @@ -136,7 +139,7 @@ void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) bool fReconciliate = !(this->parent->openFlags & kXMPFiles_OpenOnlyXMP); XMP_StringPtr packetStr = xmpPacket.c_str(); - XMP_StringLen packetLen = xmpPacket.size(); + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); if ( packetLen == 0 ) return; LFA_FileRef fileRef ( this->parent->fileRef ); @@ -159,42 +162,42 @@ void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) 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(), strTitle.size() ); + 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(), strDate.size() ); + mp3CreateDateChunk4, strDate.c_str(), (unsigned long)strDate.size() ); } else { ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion, - mp3CreateDateChunk3, strDate.c_str(), strDate.size() ); + 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(), strArtist.size() ); + 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(), strAlbum.size() ); + 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(), strGenre.size() ); + 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(), strComment.size() ); + 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(), strTrack.size() ); + mp3TrackChunk, strTrack.c_str(), (unsigned long)strTrack.size() ); } @@ -257,7 +260,7 @@ void MP3_MetaHandler::CacheFileData() this->packetInfo.offset = xmpOffset; this->packetInfo.length = bufferSize; this->xmpPacket.assign(buffer.data(), bufferSize); - this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), this->xmpPacket.size() ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); this->containsXMP = true; } @@ -337,3 +340,7 @@ bool MP3_MetaHandler::LoadPropertyFromID3 ( LFA_FileRef inFileRef, char * strFra return false; } // WAV_MetaHandler::LoadPropertyFromID3 + +// ================================================================================================= + +#endif // XMP_UNIXBuild diff --git a/source/XMPFiles/FileHandlers/MP3_Handler.hpp b/source/XMPFiles/FileHandlers/MP3_Handler.hpp index f9aa46d..8a321e6 100644 --- a/source/XMPFiles/FileHandlers/MP3_Handler.hpp +++ b/source/XMPFiles/FileHandlers/MP3_Handler.hpp @@ -10,6 +10,9 @@ // 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" // ================================================================================================= @@ -53,4 +56,5 @@ private: // ================================================================================================= +#endif // XMP_UNIXBuild #endif /* __MP3_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/MPEG_Handler.cpp b/source/XMPFiles/FileHandlers/MPEG2_Handler.cpp index 674c5e3..9c2eb48 100644 --- a/source/XMPFiles/FileHandlers/MPEG_Handler.cpp +++ b/source/XMPFiles/FileHandlers/MPEG2_Handler.cpp @@ -7,19 +7,19 @@ // of the Adobe license agreement accompanying it. // ================================================================================================= -#if WIN_ENV +#if XMP_WinBuild #pragma warning ( disable : 4996 ) // '...' was declared deprecated #endif -#include "MPEG_Handler.hpp" +#include "MPEG2_Handler.hpp" using namespace std; // ================================================================================================= -/// \file MPEG_Handler.cpp -/// \brief File format handler for MPEG. +/// \file MPEG2_Handler.cpp +/// \brief File format handler for MPEG2. /// -/// BLECH! YUCK! GAG! MPEG is done using a sidecar and recognition only by file extension! BARF!!!!! +/// BLECH! YUCK! GAG! MPEG-2 is done using a sidecar and recognition only by file extension! BARF!!!!! /// // ================================================================================================= @@ -46,64 +46,64 @@ static inline XMP_StringPtr FindFileExtension ( XMP_StringPtr filePath ) } // FindFileExtension // ================================================================================================= -// MPEG_MetaHandlerCTor -// ==================== +// MPEG2_MetaHandlerCTor +// ===================== -XMPFileHandler * MPEG_MetaHandlerCTor ( XMPFiles * parent ) +XMPFileHandler * MPEG2_MetaHandlerCTor ( XMPFiles * parent ) { - return new MPEG_MetaHandler ( parent ); + return new MPEG2_MetaHandler ( parent ); -} // MPEG_MetaHandlerCTor +} // MPEG2_MetaHandlerCTor // ================================================================================================= -// MPEG_CheckFormat -// ================ +// MPEG2_CheckFormat +// ================= -// The MPEG handler uses just the file extension, not the file content. Worse yet, it also uses a +// The MPEG-2 handler uses just the file extension, not the file content. Worse yet, it also uses a // sidecar file for the XMP. This works better if the handler owns the file, we open the sidecar -// instead of the actual MPEG file. +// instead of the actual MPEG-2 file. -bool MPEG_CheckFormat ( XMP_FileFormat format, +bool MPEG2_CheckFormat ( XMP_FileFormat format, XMP_StringPtr filePath, LFA_FileRef fileRef, XMPFiles * parent ) { IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(fileRef); - XMP_Assert ( format == kXMP_MPEGFile ); + XMP_Assert ( (format == kXMP_MPEGFile) || (format == kXMP_MPEG2File) ); XMP_Assert ( fileRef == 0 ); - return ( parent->format == kXMP_MPEGFile ); // ! Just use the first call's format hint. + return ( (parent->format == kXMP_MPEGFile) || (parent->format == kXMP_MPEGFile) ); // ! Just use the first call's format hint. -} // MPEG_CheckFormat +} // MPEG2_CheckFormat // ================================================================================================= -// MPEG_MetaHandler::MPEG_MetaHandler -// ================================== +// MPEG2_MetaHandler::MPEG2_MetaHandler +// ==================================== -MPEG_MetaHandler::MPEG_MetaHandler ( XMPFiles * _parent ) +MPEG2_MetaHandler::MPEG2_MetaHandler ( XMPFiles * _parent ) { this->parent = _parent; - this->handlerFlags = kMPEG_HandlerFlags; + this->handlerFlags = kMPEG2_HandlerFlags; this->stdCharForm = kXMP_Char8Bit; -} // MPEG_MetaHandler::MPEG_MetaHandler +} // MPEG2_MetaHandler::MPEG2_MetaHandler // ================================================================================================= -// MPEG_MetaHandler::~MPEG_MetaHandler -// =================================== +// MPEG2_MetaHandler::~MPEG2_MetaHandler +// ===================================== -MPEG_MetaHandler::~MPEG_MetaHandler() +MPEG2_MetaHandler::~MPEG2_MetaHandler() { // Nothing to do. -} // MPEG_MetaHandler::~MPEG_MetaHandler +} // MPEG2_MetaHandler::~MPEG2_MetaHandler // ================================================================================================= -// MPEG_MetaHandler::CacheFileData -// =============================== +// MPEG2_MetaHandler::CacheFileData +// ================================ -void MPEG_MetaHandler::CacheFileData() +void MPEG2_MetaHandler::CacheFileData() { bool readOnly = (! (this->parent->openFlags & kXMPFiles_OpenForUpdate)); @@ -111,7 +111,7 @@ void MPEG_MetaHandler::CacheFileData() this->processedXMP = true; // Whatever we do here is all that we do for XMPFiles::OpenFile. // Try to open the sidecar XMP file. Tolerate an open failure, there might not be any XMP. - // Note that MPEG_CheckFormat can't save the sidecar path because the handler doesn't exist then. + // Note that MPEG2_CheckFormat can't save the sidecar path because the handler doesn't exist then. XMP_StringPtr filePath = this->parent->filePath.c_str(); XMP_StringPtr extPtr = FindFileExtension ( filePath ); @@ -139,7 +139,7 @@ void MPEG_MetaHandler::CacheFileData() // Try to create a file if it does not yet exist. // *** Could someday check for a permission failure versus no .xmp file. this->parent->fileRef = LFA_Create ( this->sidecarPath.c_str() ); - if ( this->parent->fileRef == 0 ) XMP_Throw ( "Can't create MPEG sidecar", kXMPErr_ExternalFailure ); + if ( this->parent->fileRef == 0 ) XMP_Throw ( "Can't create MPEG-2 sidecar", kXMPErr_ExternalFailure ); } } @@ -158,18 +158,18 @@ void MPEG_MetaHandler::CacheFileData() this->parent->fileRef = 0; } - this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), this->xmpPacket.size() ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); this->containsXMP = true; } -} // MPEG_MetaHandler::CacheFileData +} // MPEG2_MetaHandler::CacheFileData // ================================================================================================= -// MPEG_MetaHandler::UpdateFile -// ============================ +// MPEG2_MetaHandler::UpdateFile +// ============================= -void MPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) +void MPEG2_MetaHandler::UpdateFile ( bool doSafeUpdate ) { if ( ! this->needsUpdate ) return; @@ -177,7 +177,7 @@ void MPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) XMP_Assert ( fileRef != 0 ); XMP_StringPtr packetStr = this->xmpPacket.c_str(); - XMP_StringLen packetLen = this->xmpPacket.size(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); if ( ! doSafeUpdate ) { @@ -218,17 +218,17 @@ void MPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) this->needsUpdate = false; -} // MPEG_MetaHandler::UpdateFile +} // MPEG2_MetaHandler::UpdateFile // ================================================================================================= -// MPEG_MetaHandler::WriteFile -// =========================== +// MPEG2_MetaHandler::WriteFile +// ============================ -void MPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, +void MPEG2_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) { IgnoreParam(sourceRef); IgnoreParam(sourcePath); - XMP_Throw ( "MPEG_MetaHandler::WriteFile: Should never be called", kXMPErr_Unavailable ); + XMP_Throw ( "MPEG2_MetaHandler::WriteFile: Should never be called", kXMPErr_Unavailable ); -} // MPEG_MetaHandler::WriteFile +} // MPEG2_MetaHandler::WriteFile diff --git a/source/XMPFiles/FileHandlers/MPEG_Handler.hpp b/source/XMPFiles/FileHandlers/MPEG2_Handler.hpp index 21484ce..8bb0464 100644 --- a/source/XMPFiles/FileHandlers/MPEG_Handler.hpp +++ b/source/XMPFiles/FileHandlers/MPEG2_Handler.hpp @@ -1,5 +1,5 @@ -#ifndef __MPEG_Handler_hpp__ -#define __MPEG_Handler_hpp__ 1 +#ifndef __MPEG2_Handler_hpp__ +#define __MPEG2_Handler_hpp__ 1 // ================================================================================================= // ADOBE SYSTEMS INCORPORATED @@ -13,45 +13,45 @@ #include "XMPFiles_Impl.hpp" // ================================================================================================= -/// \file MPEG_Handler.hpp -/// \brief File format handler for MPEG. +/// \file MPEG2_Handler.hpp +/// \brief File format handler for MPEG2. /// /// This header ... /// // ================================================================================================= -extern XMPFileHandler * MPEG_MetaHandlerCTor ( XMPFiles * parent ); +extern XMPFileHandler * MPEG2_MetaHandlerCTor ( XMPFiles * parent ); -extern bool MPEG_CheckFormat ( XMP_FileFormat format, +extern bool MPEG2_CheckFormat ( XMP_FileFormat format, XMP_StringPtr filePath, LFA_FileRef fileRef, XMPFiles * parent); -static const XMP_OptionBits kMPEG_HandlerFlags = ( kXMPFiles_CanInjectXMP | - kXMPFiles_CanExpand | - kXMPFiles_CanRewrite | - kXMPFiles_AllowsOnlyXMP | - kXMPFiles_ReturnsRawPacket | - kXMPFiles_HandlerOwnsFile | - kXMPFiles_AllowsSafeUpdate | - kXMPFiles_UsesSidecarXMP ); +static const XMP_OptionBits kMPEG2_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_UsesSidecarXMP ); -class MPEG_MetaHandler : public XMPFileHandler +class MPEG2_MetaHandler : public XMPFileHandler { public: std::string sidecarPath; - MPEG_MetaHandler ( XMPFiles * parent ); - ~MPEG_MetaHandler(); + MPEG2_MetaHandler ( XMPFiles * parent ); + ~MPEG2_MetaHandler(); void CacheFileData(); void UpdateFile ( bool doSafeUpdate ); void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); -}; // MPEG_MetaHandler +}; // MPEG2_MetaHandler // ================================================================================================= -#endif /* __MPEG_Handler_hpp__ */ +#endif /* __MPEG2_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp b/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp new file mode 100644 index 0000000..a3f92d6 --- /dev/null +++ b/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp @@ -0,0 +1,909 @@ +// ================================================================================================= +// 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 "MPEG4_Handler.hpp" + +#include "UnicodeConversions.hpp" +#include "MD5.h" + +#if XMP_WinBuild + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +using namespace std; + +// ================================================================================================= +/// \file MPEG4_Handler.cpp +/// \brief File format handler for MPEG-4, a flavor of the ISO Base Media File Format. +/// +/// This handler ... +/// +// ================================================================================================= + +// File and box type codes as big endian 32-bit integers, allows faster comparisons. + +static XMP_Uns32 kBE_ftyp = MakeUns32BE ( 0x66747970UL ); // File header Box, no version/flags. + +static XMP_Uns32 kBE_mp41 = MakeUns32BE ( 0x6D703431UL ); // Compatibility codes +static XMP_Uns32 kBE_mp42 = MakeUns32BE ( 0x6D703432UL ); +static XMP_Uns32 kBE_f4v = MakeUns32BE ( 0x66347620UL ); + +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. + +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))) + +// ================================================================================================= + +// Pairs of 3 letter ISO 639-2 codes mapped to 2 letter ISO 639-1 codes from: +// http://www.loc.gov/standards/iso639-2/php/code_list.php +// Would have to write an "==" operator to use std::map, must compare values not pointers. +// ! Not fully sorted, do not use a binary search. +static XMP_StringPtr kKnownLangs[] = + { "aar", "aa", "abk", "ab", "afr", "af", "aka", "ak", "alb", "sq", "sqi", "sq", "amh", "am", + "ara", "ar", "arg", "an", "arm", "hy", "hye", "hy", "asm", "as", "ava", "av", "ave", "ae", + "aym", "ay", "aze", "az", "bak", "ba", "bam", "bm", "baq", "eu", "eus", "eu", "bel", "be", + "ben", "bn", "bih", "bh", "bis", "bi", "bod", "bo", "tib", "bo", "bos", "bs", "bre", "br", + "bul", "bg", "bur", "my", "mya", "my", "cat", "ca", "ces", "cs", "cze", "cs", "cha", "ch", + "che", "ce", "chi", "zh", "zho", "zh", "chu", "cu", "chv", "cv", "cor", "kw", "cos", "co", + "cre", "cr", "cym", "cy", "wel", "cy", "cze", "cs", "ces", "cs", "dan", "da", "deu", "de", + "ger", "de", "div", "dv", "dut", "nl", "nld", "nl", "dzo", "dz", "ell", "el", "gre", "el", + "eng", "en", "epo", "eo", "est", "et", "eus", "eu", "baq", "eu", "ewe", "ee", "fao", "fo", + "fas", "fa", "per", "fa", "fij", "fj", "fin", "fi", "fra", "fr", "fre", "fr", "fre", "fr", + "fra", "fr", "fry", "fy", "ful", "ff", "geo", "ka", "kat", "ka", "ger", "de", "deu", "de", + "gla", "gd", "gle", "ga", "glg", "gl", "glv", "gv", "gre", "el", "ell", "el", "grn", "gn", + "guj", "gu", "hat", "ht", "hau", "ha", "heb", "he", "her", "hz", "hin", "hi", "hmo", "ho", + "hrv", "hr", "scr", "hr", "hun", "hu", "hye", "hy", "arm", "hy", "ibo", "ig", "ice", "is", + "isl", "is", "ido", "io", "iii", "ii", "iku", "iu", "ile", "ie", "ina", "ia", "ind", "id", + "ipk", "ik", "isl", "is", "ice", "is", "ita", "it", "jav", "jv", "jpn", "ja", "kal", "kl", + "kan", "kn", "kas", "ks", "kat", "ka", "geo", "ka", "kau", "kr", "kaz", "kk", "khm", "km", + "kik", "ki", "kin", "rw", "kir", "ky", "kom", "kv", "kon", "kg", "kor", "ko", "kua", "kj", + "kur", "ku", "lao", "lo", "lat", "la", "lav", "lv", "lim", "li", "lin", "ln", "lit", "lt", + "ltz", "lb", "lub", "lu", "lug", "lg", "mac", "mk", "mkd", "mk", "mah", "mh", "mal", "ml", + "mao", "mi", "mri", "mi", "mar", "mr", "may", "ms", "msa", "ms", "mkd", "mk", "mac", "mk", + "mlg", "mg", "mlt", "mt", "mol", "mo", "mon", "mn", "mri", "mi", "mao", "mi", "msa", "ms", + "may", "ms", "mya", "my", "bur", "my", "nau", "na", "nav", "nv", "nbl", "nr", "nde", "nd", + "ndo", "ng", "nep", "ne", "nld", "nl", "dut", "nl", "nno", "nn", "nob", "nb", "nor", "no", + "nya", "ny", "oci", "oc", "oji", "oj", "ori", "or", "orm", "om", "oss", "os", "pan", "pa", + "per", "fa", "fas", "fa", "pli", "pi", "pol", "pl", "por", "pt", "pus", "ps", "que", "qu", + "roh", "rm", "ron", "ro", "rum", "ro", "rum", "ro", "ron", "ro", "run", "rn", "rus", "ru", + "sag", "sg", "san", "sa", "scc", "sr", "srp", "sr", "scr", "hr", "hrv", "hr", "sin", "si", + "slk", "sk", "slo", "sk", "slo", "sk", "slk", "sk", "slv", "sl", "sme", "se", "smo", "sm", + "sna", "sn", "snd", "sd", "som", "so", "sot", "st", "spa", "es", "sqi", "sq", "alb", "sq", + "srd", "sc", "srp", "sr", "scc", "sr", "ssw", "ss", "sun", "su", "swa", "sw", "swe", "sv", + "tah", "ty", "tam", "ta", "tat", "tt", "tel", "te", "tgk", "tg", "tgl", "tl", "tha", "th", + "tib", "bo", "bod", "bo", "tir", "ti", "ton", "to", "tsn", "tn", "tso", "ts", "tuk", "tk", + "tur", "tr", "twi", "tw", "uig", "ug", "ukr", "uk", "urd", "ur", "uzb", "uz", "ven", "ve", + "vie", "vi", "vol", "vo", "wel", "cy", "cym", "cy", "wln", "wa", "wol", "wo", "xho", "xh", + "yid", "yi", "yor", "yo", "zha", "za", "zho", "zh", "chi", "zh", "zul", "zu", + 0, 0 }; + +static 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 + +// ================================================================================================= +// 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. +// +// The 'ftyp' box structure 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. + +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. + + 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; + + 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 ( 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. + + // 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; + + } + + return false; + +} // MPEG4_CheckFormat + +// ================================================================================================= +// MPEG4_MetaHandlerCTor +// ===================== + +XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent ) +{ + + return new MPEG4_MetaHandler ( parent ); + +} // MPEG4_MetaHandlerCTor + +// ================================================================================================= +// MPEG4_MetaHandler::MPEG4_MetaHandler +// ==================================== + +MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent ) : xmpBoxPos(0) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kMPEG4_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} // MPEG4_MetaHandler::MPEG4_MetaHandler + +// ================================================================================================= +// MPEG4_MetaHandler::~MPEG4_MetaHandler +// ===================================== + +MPEG4_MetaHandler::~MPEG4_MetaHandler() +{ + + // Nothing to do yet. + +} // 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. + +// ! We're returning the content size, not the raw (full) MPEG-4 box size! + +static void GetBoxInfo ( LFA_FileRef fileRef, XMP_Uns64 fileSize, XMP_Uns64 boxPos, + XMP_Uns32 * boxType, XMP_Uns64 * headerSize, XMP_Uns64 * contentSize ) +{ + XMP_Uns8 buffer [8]; + XMP_Uns32 u32Size; + + LFA_Seek ( fileRef, boxPos, SEEK_SET ); + (void) LFA_Read ( fileRef, buffer, 8, kLFA_RequireAll ); + + u32Size = GetUns32BE ( &buffer[0] ); + *boxType = Get4CharCode ( &buffer[4] ); + + 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; + } + +} // GetBoxInfo + +// ================================================================================================= +// 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. + +void MPEG4_MetaHandler::CacheFileData() +{ + XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + + LFA_FileRef fileRef = this->parent->fileRef; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + XMP_Uns64 fileSize = LFA_Measure ( fileRef ); + XMP_Uns64 outerPos, outerSize, hSize, cSize; + XMP_Uns32 boxType; + + // The outer loop looks for the top level 'moov' and 'uuid'/XMP boxes. + + bool moovFound = false; + if ( this->parent->openFlags & kXMPFiles_OpenOnlyXMP ) moovFound = true; // Ignore legacy. + + for ( outerPos = 0; (outerPos < fileSize) && ((! this->containsXMP) || (! moovFound)); outerPos += outerSize ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); + } + + GetBoxInfo ( fileRef, fileSize, outerPos, &boxType, &hSize, &cSize ); + outerSize = hSize + cSize; + + if ( (! this->containsXMP) && (boxType == kBE_uuid) ) { + + XMP_Uns8 uuid [16]; + LFA_Read ( fileRef, uuid, 16, kLFA_RequireAll ); + + if ( memcmp ( uuid, kBE_xmpUUID, 16 ) == 0 ) { + + // Found the XMP, record the offset and size, read the packet. + + this->containsXMP = true; + this->xmpBoxPos = outerPos; + this->packetInfo.offset = outerPos + hSize + 16; + this->packetInfo.length = (XMP_Int32) (cSize - 16); + + 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 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; + + bool mvhdFound = false, udtaFound = false; + + for ( middlePos = middleStart; (middlePos < middleEnd) && ((! mvhdFound) || (! udtaFound)); middlePos += middleSize ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); + } + + GetBoxInfo ( fileRef, fileSize, middlePos, &boxType, &hSize, &cSize ); + middleSize = hSize + cSize; + + if ( (! mvhdFound) && (boxType == kBE_mvhd) ) { + + // 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 ); + + } else if ( (! udtaFound) && (boxType == kBE_udta) ) { + + // 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; + + for ( innerPos = innerStart; innerPos < innerEnd; innerPos += innerSize ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); + } + + GetBoxInfo ( fileRef, fileSize, innerPos, &boxType, &hSize, &cSize ); + innerSize = hSize + cSize; + if ( boxType != kBE_cprt ) continue; + + // ! 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 ); + + } // inner loop + + } // 'moov'/'udta' box + + } // middle loop + + } // 'moov' box + + } // outer loop + +} // MPEG4_MetaHandler::CacheFileData + +// ================================================================================================= +// MPEG4_MetaHandler::MakeLegacyDigest +// =================================== + +// *** Will need updating if we process the 'ilst' metadata. + +#define kHexDigits "0123456789ABCDEF" + +void MPEG4_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + MD5_CTX context; + unsigned char digestBin [16]; + MD5Init ( &context ); + + MD5Update ( &context, (XMP_Uns8*)this->mvhdBox.c_str(), (unsigned int) this->mvhdBox.size() ); + + 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() ); + } + + 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 ); + +} // MPEG4_MetaHandler::MakeLegacyDigest + +// ================================================================================================= + +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 + +static void ExtractMVHD_v0 ( XMP_Uns8 * buffer, MVHD_v1 * mvhd ) // Always convert to the v1 form. +{ + 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] ); +} + +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] ); +} + +// ================================================================================================= +// MPEG4_MetaHandler::ProcessXMP +// ============================= + +#define kAlmostMaxSeconds 0x7FFFFF00 + +void MPEG4_MetaHandler::ProcessXMP() +{ + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + if ( this->mvhdBox.empty() && this->cprtBoxes.empty() ) return; // No legacy, we're done. + + std::string oldDigest; + bool oldDigestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "MPEG-4", &oldDigest, 0 ); + + if ( oldDigestFound ) { + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + if ( oldDigest == newDigest ) return; // No legacy changes. + } + + // 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. + + // *** 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. + + if ( ! this->mvhdBox.empty() ) { + + MVHD_v1 mvhd; + XMP_DateTime xmpDate; + + 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 ); + } + + if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "CreateDate" )) ) { + if ( (mvhd.creationTime >> 32) < 0xFF ) { // Sanity check for bogus date info. + + 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; + + } + } + + if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "ModifyDate" )) ) { + if ( (mvhd.modificationTime >> 32) < 0xFF ) { // Sanity check for bogus date info. + + 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; + + 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 ( 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; + } + } + + } + + if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "rights" )) ) { + + std::string tempStr; + char lang3 [4]; // The unpacked ISO-639-2/T language code. + lang3[3] = 0; + + for ( size_t i = 0, limit = this->cprtBoxes.size(); i < limit; ++i ) { + if ( this->cprtBoxes[i].size() < 7 ) continue; + + 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. + + 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]; + + if ( (rawLen >= 8) && (GetUns16BE ( xmpValue ) == 0xFEFF) ) { + FromUTF16 ( (UTF16Unit*)xmpValue, rawLen/2, &tempStr, true /* big endian */ ); + xmpValue = tempStr.c_str(); + } + + this->xmpObj.SetLocalizedText ( kXMP_NS_DC, "rights", xmpLang, "", xmpValue ); + this->containsXMP = true; + + } + + } + +} // MPEG4_MetaHandler::ProcessXMP + +// ================================================================================================= +// 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'. + +void MPEG4_MetaHandler::PickNewLocation() +{ + LFA_FileRef fileRef = this->parent->fileRef; + XMP_Uns64 fileSize = LFA_Measure ( fileRef ); + + XMP_Uns32 xmpBoxSize = 4+4+16 + (XMP_Uns32)this->xmpPacket.size(); + + XMP_Uns64 currPos, prevPos; // Info about the most recent 2 boxes. + XMP_Uns32 currType, prevType; + XMP_Uns64 currSize, prevSize, hSize, cSize; + + XMP_Uns32 be32Size; + XMP_Uns64 be64Size; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + bool pastMDAT = false; + + currType = 0; + prevPos = prevSize = currSize = 0; + for ( currPos = 0; currPos < fileSize; currPos += currSize ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort ); + } + + 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 ); + + GetBoxInfo ( fileRef, fileSize, currPos, &currType, &hSize, &cSize ); + currSize = hSize + cSize; + + 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; + + 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 ( currPos < fileSize ) { + + // 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. + + this->xmpBoxPos = currPos; + XMP_Assert ( (currType == kBE_free) && (currSize >= xmpBoxSize) ); + + 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 ); + } + } + + } else { + + // Appending the XMP, make sure the current final box has an explicit size. + + this->xmpBoxPos = fileSize; + XMP_Assert ( currPos == fileSize ); + + 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 ); + + if ( be32Size == 0 ) { + + // 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'. + + if ( currSize <= 0xFFFFFFFFULL ) { + + // 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 ); + + } else if ( prevType != kBE_free ) { + + // 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 if ( prevSize == 8 ) { + + // 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 { + + // Trim 8 bytes off the end of the free box. + + 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 ); + } 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 ); + + } + + } + + } + +} // MPEG4_MetaHandler::PickNewLocation + +// ================================================================================================= +// 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! + +void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) 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. + + if ( this->xmpBoxPos != 0 ) { + + XMP_Uns32 boxType; + XMP_Uns64 hSize, cSize; + XMP_Uns8 uuid [16]; + + GetBoxInfo ( fileRef, LFA_Measure ( fileRef ), this->xmpBoxPos, &boxType, &hSize, &cSize ); + LFA_Read ( fileRef, uuid, 16, kLFA_RequireAll ); + + 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 ); + } + + } + + #endif + + if ( (this->xmpBoxPos != 0) && (this->xmpPacket.size() <= (size_t)this->packetInfo.length) ) { + + // 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 ); + LFA_Seek ( fileRef, this->packetInfo.offset, SEEK_SET ); + LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() ); + + } else if ( (this->xmpBoxPos != 0) && + ((XMP_Uns64)(this->packetInfo.offset + this->packetInfo.length) == fileSize) ) { + + // 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() ); + + } 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 ); + } + + this->PickNewLocation(); // ! Might increase the size of the XMP packet. + + 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() ); + + } + +} // MPEG4_MetaHandler::UpdateFile + +// ================================================================================================= +// MPEG4_MetaHandler::WriteFile +// ============================ +// +// Since the XMP and legacy is probably a miniscule part of the entire file, and since we can't +// change the offset of most of the boxes, just copy the entire source file to the dest file, then +// do an in-place update to the destination file. + +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 new file mode 100644 index 0000000..9da2741 --- /dev/null +++ b/source/XMPFiles/FileHandlers/MPEG4_Handler.hpp @@ -0,0 +1,69 @@ +#ifndef __MPEG4_Handler_hpp__ +#define __MPEG4_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 MPEG4_Handler.hpp +/// \brief File format handler for MPEG-4. +/// +/// This header ... +/// +// ================================================================================================ + +extern XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool MPEG4_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kMPEG4_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate + ); + +class MPEG4_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); + + MPEG4_MetaHandler ( XMPFiles * _parent ); + virtual ~MPEG4_MetaHandler(); + +private: + + MPEG4_MetaHandler() : xmpBoxPos(0) {}; // Hidden on purpose. + + void MakeLegacyDigest ( std::string * digestStr ); + void PickNewLocation(); + + XMP_Uns64 xmpBoxPos; // The file offset of the XMP box (the size field, not the content). + + std::string mvhdBox; // ! Both contain binary data, but std::string is handy. + std::vector<std::string> cprtBoxes; + +}; // MPEG4_MetaHandler + +// ================================================================================================= + +#endif // __MPEG4_Handler_hpp__ diff --git a/source/XMPFiles/FileHandlers/P2_Handler.cpp b/source/XMPFiles/FileHandlers/P2_Handler.cpp new file mode 100644 index 0000000..eac0edf --- /dev/null +++ b/source/XMPFiles/FileHandlers/P2_Handler.cpp @@ -0,0 +1,1203 @@ +// ================================================================================================= +// 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 "P2_Handler.hpp" + +#include "MD5.h" + +using namespace std; + +// ================================================================================================= +/// \file P2_Handler.cpp +/// \brief Folder format handler for P2. +/// +/// This handler is for the P2 video format. This is a pseudo-package, visible files but with a very +/// well-defined layout and naming rules. A typical P2 example looks like: +/// +/// .../MyMovie +/// CONTENTS/ +/// CLIP/ +/// 0001AB.XML +/// 0001AB.XMP +/// 0002CD.XML +/// 0002CD.XMP +/// VIDEO/ +/// 0001AB.MXF +/// 0002CD.MXF +/// AUDIO/ +/// 0001AB00.MXF +/// 0001AB01.MXF +/// 0002CD00.MXF +/// 0002CD01.MXF +/// ICON/ +/// 0001AB.BMP +/// 0002CD.BMP +/// VOICE/ +/// 0001AB.WAV +/// 0002CD.WAV +/// PROXY/ +/// 0001AB.MP4 +/// 0002CD.MP4 +/// +/// From the user's point of view, .../MyMovie contains P2 stuff, in this case 2 clips whose raw +/// names are 0001AB and 0002CD. There may be mapping information for nicer clip names to the raw +/// names, but that can be ignored for now. Each clip is stored as a collection of files, each file +/// holding some specific aspect of the clip's data. +/// +/// The P2 handler operates on clips. The path from the client of XMPFiles can be either a logical +/// clip path, like ".../MyMovie/0001AB", or a full path to one of the files. In the latter case the +/// handler must figure out the intended clip, it must not blindly use the named file. +/// +/// Once the P2 structure and intended clip are identified, the handler only deals with the .XMP and +/// .XML files in the CLIP folder. The .XMP file, if present, contains the XMP for the clip. The .XML +/// file must be present to define the existance of the clip. It contains a variety of information +/// about the clip, including some legacy metadata. +/// +// ================================================================================================= + +static const char * kContentFolderNames[] = { "CLIP", "VIDEO", "AUDIO", "ICON", "VOICE", "PROXY", 0 }; +static int kNumRequiredContentFolders = 6; // All 6 of the above. + +static inline bool CheckContentFolderName ( const std::string & folderName ) +{ + for ( int i = 0; kContentFolderNames[i] != 0; ++i ) { + if ( folderName == kContentFolderNames[i] ) return true; + } + return false; +} + +// ================================================================================================= +// InternalMakeClipFilePath +// ======================== +// +// P2_CheckFormat can't use the member function. + +static void InternalMakeClipFilePath ( std::string * path, + const std::string & rootPath, + const std::string & clipName, + XMP_StringPtr suffix ) +{ + + *path = rootPath; + *path += kDirChar; + *path += "CONTENTS"; + *path += kDirChar; + *path += "CLIP"; + *path += kDirChar; + *path += clipName; + *path += suffix; + +} // InternalMakeClipFilePath + +// ================================================================================================= +// P2_CheckFormat +// ============== +// +// This version does fairly simple checks. The top level folder (.../MyMovie) must a child folder +// called CONTENTS. This must have a subfolder called CLIP. It may also have subfolders called +// VIDEO, AUDIO, ICON, VOICE, and PROXY. Any mixture of these additional folders is allowed, but no +// other children are allowed in CONTENTS. The CLIP folder must contain a .XML file for the desired +// clip. The name checks are case insensitive. +// +// The state of the string parameters depends on the form of the path passed by the client. If the +// client passed a logical clip path, like ".../MyMovie/0001AB", the parameters are: +// rootPath - ".../MyMovie" +// gpName - empty +// parentName - empty +// leafName - "0001AB" +// If the client passed a full file path, like ".../MyMovie/CONTENTS/VOICE/0001AB.WAV", they are: +// rootPath - ".../MyMovie" +// gpName - "CONTENTS" +// parentName - "VOICE" +// leafName - "0001AB" +// +// For most of the content files the base file name is the raw clip name. Files in the AUDIO and +// VOICE folders have an extra 2 digits appended to the raw clip name. These must be trimmed. + +// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +bool P2_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ) +{ + XMP_FolderInfo folderInfo; + std::string tempPath, childName; + + std::string clipName = leafName; + + // Do some basic checks on the gpName and parentName. + + if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty. + + if ( ! gpName.empty() ) { + + if ( gpName != "CONTENTS" ) return false; + if ( ! CheckContentFolderName ( parentName ) ) return false; + + if ( (parentName == "AUDIO") | (parentName == "VOICE") ) { + if ( clipName.size() < 3 ) return false; + clipName.erase ( clipName.size() - 2 ); + } + + } + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += "CONTENTS"; + if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFolder ) return false; + + folderInfo.Open ( tempPath.c_str() ); + int numChildrenFound = 0; + while ( ( folderInfo.GetNextChild ( &childName ) && ( numChildrenFound < kNumRequiredContentFolders ) ) ) { // Make sure the children of CONTENTS are legit. + if ( CheckContentFolderName ( childName ) ) { + folderInfo.GetFolderPath ( &tempPath ); + tempPath += kDirChar; + tempPath += childName; + if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFolder ) return false; + ++numChildrenFound; + } + } + folderInfo.Close(); + + // Make sure the clip's .XML file exists. + + InternalMakeClipFilePath ( &tempPath, rootPath, clipName, ".XML" ); + if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false; + + // Make a bogus path to pass the root path and clip name to the handler. A bit of a hack, but + // the only way to get info from here to there. + + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += clipName; + + size_t pathLen = tempPath.size() + 1; // Include a terminating nul. + parent->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. + + return true; + +} // P2_CheckFormat + +// ================================================================================================= +// P2_MetaHandlerCTor +// ================== + +XMPFileHandler * P2_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new P2_MetaHandler ( parent ); + +} // P2_MetaHandlerCTor + +// ================================================================================================= +// P2_MetaHandler::P2_MetaHandler +// ============================== + +P2_MetaHandler::P2_MetaHandler ( XMPFiles * _parent ) : expat(0), clipMetadata(0), clipContent(0) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kP2_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + // Extract the root path and clip name from handlerTemp. + + XMP_Assert ( this->parent->handlerTemp != 0 ); + this->rootPath = (char*)this->parent->handlerTemp; + free ( this->parent->handlerTemp ); + this->parent->handlerTemp = 0; + + SplitLeafName ( &this->rootPath, &this->clipName ); + +} // P2_MetaHandler::P2_MetaHandler + +// ================================================================================================= +// P2_MetaHandler::~P2_MetaHandler +// =============================== + +P2_MetaHandler::~P2_MetaHandler() +{ + + this->CleanupLegacyXML(); + if ( this->parent->handlerTemp != 0 ) { + free ( this->parent->handlerTemp ); + this->parent->handlerTemp = 0; + } + +} // P2_MetaHandler::~P2_MetaHandler + +// ================================================================================================= +// P2_MetaHandler::MakeClipFilePath +// ================================ + +void P2_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix ) +{ + + InternalMakeClipFilePath ( path, this->rootPath, this->clipName, suffix ); + +} // P2_MetaHandler::MakeClipFilePath + +// ================================================================================================= +// P2_MetaHandler::CleanupLegacyXML +// ================================ + +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; } + + clipMetadata = 0; // ! Was a pointer into the expat tree. + clipContent = 0; // ! Was a pointer into the expat tree. + +} // P2_MetaHandler::CleanupLegacyXML + +// ================================================================================================= +// P2_MetaHandler::DigestLegacyItem +// ================================ + +void P2_MetaHandler::DigestLegacyItem ( MD5_CTX & md5Context, XML_NodePtr legacyContext, XMP_StringPtr legacyPropName ) +{ + XML_NodePtr legacyProp = legacyContext->GetNamedElement ( this->p2NS.c_str(), legacyPropName ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &md5Context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + +} // P2_MetaHandler::DigestLegacyItem + +// ================================================================================================= +// P2_MetaHandler::DigestLegacyRelations +// ===================================== + +void P2_MetaHandler::DigestLegacyRelations ( MD5_CTX & md5Context ) +{ + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_Node * legacyContext = this->clipContent->GetNamedElement ( p2NS, "Relation" ); + + if ( legacyContext != 0 ) { + + this->DigestLegacyItem ( md5Context, legacyContext, "GlobalShotID" ); + XML_Node * legacyConnectionContext = legacyContext = this->clipContent->GetNamedElement ( p2NS, "Connection" ); + + if ( legacyConnectionContext != 0 ) { + + legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Top" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" ); + } + + legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Previous" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" ); + } + + legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Next" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" ); + } + + } + + } + +} // P2_MetaHandler::DigestLegacyRelations + +// ================================================================================================= +// P2_MetaHandler::SetXMPPropertyFromLegacyXML +// =========================================== + +void P2_MetaHandler::SetXMPPropertyFromLegacyXML ( bool digestFound, + XML_NodePtr legacyContext, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr legacyPropName, + bool isLocalized ) +{ + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( schemaNS, propName )) ) { + + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyProp = legacyContext->GetNamedElement ( p2NS, legacyPropName ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + if ( isLocalized ) { + this->xmpObj.SetLocalizedText ( schemaNS, propName, "", "x-default", legacyProp->GetLeafContentValue(), kXMP_DeleteExisting ); + } else { + this->xmpObj.SetProperty ( schemaNS, propName, legacyProp->GetLeafContentValue(), kXMP_DeleteExisting ); + } + this->containsXMP = true; + } + + } + +} // P2_MetaHandler::SetXMPPropertyFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetRelationsFromLegacyXML +// ========================================= + +void P2_MetaHandler::SetRelationsFromLegacyXML ( bool digestFound ) +{ + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyRelationContext = this->clipContent->GetNamedElement ( p2NS, "Relation" ); + + // P2 Relation blocks are optional -- they're only present when a clip is part of a multi-clip shot. + + if ( legacyRelationContext != 0 ) { + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "relation" )) ) { + + XML_NodePtr legacyProp = legacyRelationContext->GetNamedElement ( p2NS, "GlobalShotID" ); + std::string relationString; + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + this->xmpObj.DeleteProperty ( kXMP_NS_DC, "relation" ); + relationString = std::string("globalShotID:") + legacyProp->GetLeafContentValue(); + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString ); + this->containsXMP = true; + + XML_NodePtr legacyConnectionContext = legacyRelationContext->GetNamedElement ( p2NS, "Connection" ); + + if ( legacyConnectionContext != 0 ) { + + XML_NodePtr legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Top" ); + + if ( legacyContext != 0 ) { + legacyProp = legacyContext->GetNamedElement ( p2NS, "GlobalClipID" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + relationString = std::string("topGlobalClipID:") + legacyProp->GetLeafContentValue(); + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString ); + } + } + + legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Previous" ); + + if ( legacyContext != 0 ) { + legacyProp = legacyContext->GetNamedElement ( p2NS, "GlobalClipID" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + relationString = std::string("previousGlobalClipID:") + legacyProp->GetLeafContentValue(); + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString ); + } + } + + legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Next" ); + + if ( legacyContext != 0 ) { + legacyProp = legacyContext->GetNamedElement ( p2NS, "GlobalClipID" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + relationString = std::string("nextGlobalClipID:") + legacyProp->GetLeafContentValue(); + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString ); + } + } + + } + + } + + } + + } + +} // P2_MetaHandler::SetRelationsFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetAudioInfoFromLegacyXML +// ========================================= + +void P2_MetaHandler::SetAudioInfoFromLegacyXML ( bool digestFound ) +{ + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyAudioContext = this->clipContent->GetNamedElement ( p2NS, "EssenceList" ); + + if ( legacyAudioContext != 0 ) { + + legacyAudioContext = legacyAudioContext->GetNamedElement ( p2NS, "Audio" ); + + if ( legacyAudioContext != 0 ) { + + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyAudioContext, kXMP_NS_DM, "audioSampleRate", "SamplingRate", false ); + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "audioSampleType" )) ) { + XML_NodePtr legacyProp = legacyAudioContext->GetNamedElement ( p2NS, "BitsPerSample" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + const std::string p2BitsPerSample = legacyProp->GetLeafContentValue(); + std::string dmSampleType; + + if ( p2BitsPerSample == "16" ) { + dmSampleType = "16Int"; + } else if ( p2BitsPerSample == "24" ) { + dmSampleType = "32Int"; + } + + if ( ! dmSampleType.empty() ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "audioSampleType", dmSampleType, kXMP_DeleteExisting ); + this->containsXMP = true; + } + + } + + } + + } + + } + +} // P2_MetaHandler::SetAudioInfoFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetVideoInfoFromLegacyXML +// ========================================= + +void P2_MetaHandler::SetVideoInfoFromLegacyXML ( bool digestFound ) +{ + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyVideoContext = this->clipContent->GetNamedElement ( p2NS, "EssenceList" ); + + if ( legacyVideoContext != 0 ) { + + legacyVideoContext = legacyVideoContext->GetNamedElement ( p2NS, "Video" ); + + if ( legacyVideoContext != 0 ) { + this->SetVideoFrameInfoFromLegacyXML ( legacyVideoContext, digestFound ); + this->SetStartTimecodeFromLegacyXML ( legacyVideoContext, digestFound ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyVideoContext, kXMP_NS_DM, "videoFrameRate", "FrameRate", false ); + } + + } + +} // P2_MetaHandler::SetVideoInfoFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetDurationFromLegacyXML +// ======================================== + +void P2_MetaHandler::SetDurationFromLegacyXML ( bool digestFound ) +{ + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) { + + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyDurationProp = this->clipContent->GetNamedElement ( p2NS, "Duration" ); + XML_NodePtr legacyEditUnitProp = this->clipContent->GetNamedElement ( p2NS, "EditUnit" ); + + if ( (legacyDurationProp != 0) && ( legacyEditUnitProp != 0 ) && + legacyDurationProp->IsLeafContentNode() && legacyEditUnitProp->IsLeafContentNode() ) { + + this->xmpObj.DeleteProperty ( kXMP_NS_DM, "duration" ); + this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", + kXMP_NS_DM, "value", legacyDurationProp->GetLeafContentValue() ); + + this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", + kXMP_NS_DM, "scale", legacyEditUnitProp->GetLeafContentValue() ); + this->containsXMP = true; + + } + + } + +} // P2_MetaHandler::SetDurationFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetVideoFrameInfoFromLegacyXML +// ============================================== + +void P2_MetaHandler::SetVideoFrameInfoFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound ) +{ + + // Map the P2 Codec field to various dynamic media schema fields. + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "videoFrameSize" )) ) { + + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "Codec" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + const std::string p2Codec = legacyProp->GetLeafContentValue(); + std::string dmPixelAspectRatio, dmVideoCompressor, dmWidth, dmHeight; + + if ( p2Codec == "DV25_411" ) { + dmWidth = "720"; + dmVideoCompressor = "DV25 4:1:1"; + } else if ( p2Codec == "DV25_420" ) { + dmWidth = "720"; + dmVideoCompressor = "DV25 4:2:0"; + } else if ( p2Codec == "DV50_422" ) { + dmWidth = "720"; + dmVideoCompressor = "DV50 4:2:2"; + } else if ( ( p2Codec == "DV100_1080/59.94i" ) || ( p2Codec == "DV100_1080/50i" ) ) { + dmVideoCompressor = "DV100"; + dmHeight = "1080"; + + if ( p2Codec == "DV100_1080/59.94i" ) { + dmWidth = "1280"; + dmPixelAspectRatio = "3/2"; + } else { + dmWidth = "1440"; + dmPixelAspectRatio = "1920/1440"; + } + } else if ( ( p2Codec == "DV100_720/59.94p" ) || ( p2Codec == "DV100_720/50p" ) ) { + dmVideoCompressor = "DV100"; + dmHeight = "720"; + dmWidth = "960"; + dmPixelAspectRatio = "1920/1440"; + } else if ( ( p2Codec == "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"; + } + + if ( dmWidth == "720" ) { + + // This is SD footage -- calculate the frame height and pixel aspect ratio using the legacy P2 + // FrameRate and AspectRatio fields. + + legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "FrameRate" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + const std::string p2FrameRate = legacyProp->GetLeafContentValue(); + + legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "AspectRatio" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + const std::string p2AspectRatio = legacyProp->GetLeafContentValue(); + + if ( p2FrameRate == "50i" ) { + // Standard Definition PAL. + dmHeight = "576"; + if ( p2AspectRatio == "4:3" ) { + dmPixelAspectRatio = "768/702"; + } else if ( p2AspectRatio == "16:9" ) { + dmPixelAspectRatio = "1024/702"; + } + } else if ( p2FrameRate == "59.94i" ) { + // Standard Definition NTSC. + dmHeight = "480"; + if ( p2AspectRatio == "4:3" ) { + dmPixelAspectRatio = "10/11"; + } else if ( p2AspectRatio == "16:9" ) { + dmPixelAspectRatio = "40/33"; + } + } + + } + } + } + + if ( ! dmPixelAspectRatio.empty() ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "videoPixelAspectRatio", dmPixelAspectRatio, kXMP_DeleteExisting ); + this->containsXMP = true; + } + + if ( ! dmVideoCompressor.empty() ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "videoCompressor", dmVideoCompressor, kXMP_DeleteExisting ); + this->containsXMP = true; + } + + if ( ( ! dmWidth.empty() ) && ( ! dmHeight.empty() ) ) { + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", dmWidth, 0 ); + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", dmHeight, 0 ); + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", "pixel", 0 ); + this->containsXMP = true; + } + + } + + } + +} // P2_MetaHandler::SetVideoFrameInfoFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetStartTimecodeFromLegacyXML +// ============================================= + +void P2_MetaHandler::SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound ) +{ + + // Translate start timecode to the format specified by the dynamic media schema. + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "startTimecode" )) ) { + + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "StartTimecode" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + std::string p2StartTimecode = legacyProp->GetLeafContentValue(); + + legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "FrameRate" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + const std::string p2FrameRate = legacyProp->GetLeafContentValue(); + const XMP_StringPtr p2DropFrameFlag = legacyProp->GetAttrValue ( "DropFrameFlag" ); + std::string dmTimeFormat; + + if ( ( p2FrameRate == "50i" ) || ( p2FrameRate == "25p" ) ) { + + dmTimeFormat = "25Timecode"; + + } else if ( p2FrameRate == "23.98p" ) { + + dmTimeFormat = "23976Timecode"; + + } else if ( p2FrameRate == "50p" ) { + + dmTimeFormat = "50Timecode"; + + } else if ( p2FrameRate == "59.94p" ) { + + if ( p2DropFrameFlag == "true" ) { + dmTimeFormat = "5994DropTimecode"; + } else if ( p2DropFrameFlag == "false" ) { + dmTimeFormat = "5994NonDropTimecode"; + } + + } else if ( ( p2FrameRate == "59.94i" ) || ( p2FrameRate == "29.97p" ) ) { + + if ( p2DropFrameFlag != 0 ) { + + if ( std::strcmp ( p2DropFrameFlag, "false" ) == 0 ) { + + dmTimeFormat = "2997NonDropTimecode"; + + } else if ( std::strcmp ( p2DropFrameFlag, "true" ) == 0 ) { + + // Drop frame NTSC timecode uses semicolons instead of colons as separators. + std::string::iterator currCharIt = p2StartTimecode.begin(); + const std::string::iterator charsEndIt = p2StartTimecode.end(); + + for ( ; currCharIt != charsEndIt; ++currCharIt ) { + if ( *currCharIt == ':' ) *currCharIt = ';'; + } + + dmTimeFormat = "2997DropTimecode"; + + } + + } + + } + + if ( ( ! p2StartTimecode.empty() ) && ( ! dmTimeFormat.empty() ) ) { + this->xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", p2StartTimecode, 0 ); + this->xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", dmTimeFormat, 0 ); + this->containsXMP = true; + } + + } + + } + + } + +} // P2_MetaHandler::SetStartTimecodeFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::ForceChildElement +// ================================= + +XML_Node * P2_MetaHandler::ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, int indent /* = 0 */ ) +{ + XML_Node * wsNode; + XML_Node * childNode = parent->GetNamedElement ( this->p2NS.c_str(), localName ); + + if ( childNode == 0 ) { + + // The indenting is a hack, assuming existing 2 spaces per level. + + wsNode = new XML_Node ( parent, "", kCDataNode ); + wsNode->value = " "; // Add 2 spaces to the existing WS before the parent's close tag. + parent->content.push_back ( wsNode ); + + childNode = new XML_Node ( parent, localName, kElemNode ); + childNode->ns = parent->ns; + childNode->nsPrefixLen = parent->nsPrefixLen; + childNode->name.insert ( 0, parent->name, 0, parent->nsPrefixLen ); + parent->content.push_back ( childNode ); + + wsNode = new XML_Node ( parent, "", kCDataNode ); + wsNode->value = '\n'; + for ( ; indent > 1; --indent ) wsNode->value += " "; // Indent less 1, to "outdent" the parent's close. + parent->content.push_back ( wsNode ); + + } + + return childNode; + +} // P2_MetaHandler::ForceChildElement + +// ================================================================================================= +// P2_MetaHandler::MakeLegacyDigest +// ================================= + +// *** Early hack version. + +#define kHexDigits "0123456789ABCDEF" + +void P2_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + digestStr->erase(); + if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML. + XMP_Assert ( this->expat != 0 ); + + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyContext; + MD5_CTX md5Context; + unsigned char digestBin [16]; + MD5Init ( &md5Context ); + + legacyContext = this->clipContent; + this->DigestLegacyItem ( md5Context, legacyContext, "ClipName" ); + this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" ); + this->DigestLegacyItem ( md5Context, legacyContext, "Duration" ); + this->DigestLegacyItem ( md5Context, legacyContext, "EditUnit" ); + this->DigestLegacyRelations ( md5Context ); + + legacyContext = this->clipContent->GetNamedElement ( p2NS, "EssenceList" ); + + if ( legacyContext != 0 ) { + + XML_NodePtr videoContext = legacyContext->GetNamedElement ( p2NS, "Video" ); + + if ( videoContext != 0 ) { + this->DigestLegacyItem ( md5Context, videoContext, "AspectRatio" ); + this->DigestLegacyItem ( md5Context, videoContext, "Codec" ); + this->DigestLegacyItem ( md5Context, videoContext, "FrameRate" ); + this->DigestLegacyItem ( md5Context, videoContext, "StartTimecode" ); + } + + XML_NodePtr audioContext = legacyContext->GetNamedElement ( p2NS, "Audio" ); + + if ( audioContext != 0 ) { + this->DigestLegacyItem ( md5Context, audioContext, "SamplingRate" ); + this->DigestLegacyItem ( md5Context, audioContext, "BitsPerSample" ); + } + + } + + legacyContext = this->clipMetadata; + this->DigestLegacyItem ( md5Context, legacyContext, "UserClipName" ); + this->DigestLegacyItem ( md5Context, legacyContext, "ShotMark" ); + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Access" ); + if ( legacyContext == 0 ) return; + + this->DigestLegacyItem ( md5Context, legacyContext, "Creator" ); + this->DigestLegacyItem ( md5Context, legacyContext, "CreationDate" ); + this->DigestLegacyItem ( md5Context, legacyContext, "LastUpdateDate" ); + + MD5Final ( digestBin, &md5Context ); + + char buffer [40]; + for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digestBin[in]; + buffer[out] = kHexDigits [ byte >> 4 ]; + buffer[out+1] = kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + digestStr->append ( buffer ); + +} // P2_MetaHandler::MakeLegacyDigest + +// ================================================================================================= +// P2_MetaHandler::CacheFileData +// ============================= + +void P2_MetaHandler::CacheFileData() +{ + XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + + // Make sure the clip's .XMP file exists. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, ".XMP" ); + + if ( GetFileMode ( xmpPath.c_str() ) != kFMode_IsFile ) return; // No XMP. + + // Read the entire .XMP file. + + bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); + char openMode = 'r'; + if ( openForUpdate ) openMode = 'w'; + + LFA_FileRef xmpFile = LFA_Open ( xmpPath.c_str(), openMode ); + if ( xmpFile == 0 ) return; // The open failed. + + XMP_Int64 xmpLen = LFA_Measure ( xmpFile ); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "P2 XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.reserve ( (size_t)xmpLen ); + this->xmpPacket.append ( (size_t)xmpLen, ' ' ); + + XMP_Int32 ioCount = LFA_Read ( xmpFile, (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen, kLFA_RequireAll ); + XMP_Assert ( ioCount == xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + XMP_Assert ( this->parent->fileRef == 0 ); + if ( openMode == 'r' ) { + LFA_Close ( xmpFile ); + } else { + this->parent->fileRef = xmpFile; + } + + this->containsXMP = true; + +} // P2_MetaHandler::CacheFileData + +// ================================================================================================= +// P2_MetaHandler::ProcessXMP +// ========================== + +void P2_MetaHandler::ProcessXMP() +{ + + // Some versions of gcc can't tolerate goto's across declarations. + // *** Better yet, avoid this cruft with self-cleaning objects. + #define CleanupAndExit \ + { \ + bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \ + if ( ! openForUpdate ) this->CleanupLegacyXML(); \ + return; \ + } + + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + // -------------------------------------------------------------- + // *** 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" ); + + AutoFile xmlFile; + xmlFile.fileRef = LFA_Open ( xmlPath.c_str(), 'r' ); + if ( xmlFile.fileRef == 0 ) return; // The open failed. + + this->expat = XMP_NewExpatAdapter(); + if ( this->expat == 0 ) XMP_Throw ( "P2_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory ); + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = LFA_Read ( xmlFile.fileRef, buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + this->expat->ParseBuffer ( 0, 0, true ); // End the parse. + + 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. + + XML_Node & xmlTree = this->expat->tree; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) { + rootElem = xmlTree.content[i]; + } + } + + if ( rootElem == 0 ) CleanupAndExit + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + if ( ! XMP_LitMatch ( rootLocalName, "P2Main" ) ) CleanupAndExit + + this->p2NS = rootElem->ns; + + // Now find ClipMetadata element and check the legacy digest. + + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyContext, legacyProp; + + legacyContext = rootElem->GetNamedElement ( p2NS, "ClipContent" ); + if ( legacyContext == 0 ) CleanupAndExit + + this->clipContent = legacyContext; // ! Save the ClipContext pointer for other use. + + legacyContext = legacyContext->GetNamedElement ( p2NS, "ClipMetadata" ); + if ( legacyContext == 0 ) CleanupAndExit + + this->clipMetadata = legacyContext; // ! Save the ClipMetadata pointer for other use. + + std::string oldDigest, newDigest; + bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "P2", &oldDigest, 0 ); + if ( digestFound ) { + this->MakeLegacyDigest ( &newDigest ); + if ( oldDigest == newDigest ) CleanupAndExit + } + + // If we get here we need find and import the actual legacy elements using the current namespace. + // Either there is no old digest in the XMP, or the digests differ. In the former case keep any + // existing XMP, in the latter case take new legacy values. + this->SetXMPPropertyFromLegacyXML ( digestFound, this->clipContent, kXMP_NS_DC, "title", "ClipName", true ); + this->SetXMPPropertyFromLegacyXML ( digestFound, this->clipContent, kXMP_NS_DC, "identifier", "GlobalClipID", false ); + this->SetDurationFromLegacyXML (digestFound ); + this->SetRelationsFromLegacyXML ( digestFound ); + this->SetXMPPropertyFromLegacyXML ( digestFound, this->clipMetadata, kXMP_NS_DM, "shotName", "UserClipName", false ); + this->SetAudioInfoFromLegacyXML ( digestFound ); + this->SetVideoInfoFromLegacyXML ( digestFound ); + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Access" ); + if ( legacyContext == 0 ) CleanupAndExit + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "creator" )) ) { + legacyProp = legacyContext->GetNamedElement ( p2NS, "Creator" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + this->xmpObj.DeleteProperty ( kXMP_NS_DC, "creator" ); + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsUnordered, + legacyProp->GetLeafContentValue() ); + this->containsXMP = true; + } + } + + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_XMP, "CreateDate", "CreationDate", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_XMP, "ModifyDate", "LastUpdateDate", false ); + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "Rating" )) ) { + legacyProp = legacyContext->GetNamedElement ( p2NS, "ShotMark" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + 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 ); + this->containsXMP = true; + } else if ((strcmp(markValue, "true") == 0) || (strcmp(markValue, "1") == 0)) { + this->xmpObj.SetProperty ( kXMP_NS_XMP, "Rating", "1", kXMP_DeleteExisting ); + this->containsXMP = true; + } + } + } + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Location" ); + + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "shotLocation", "PlaceName", false ); + } + + CleanupAndExit + #undef CleanupAndExit + +} // P2_MetaHandler::ProcessXMP + +// ================================================================================================= +// P2_MetaHandler::UpdateFile +// ========================== +// +// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here. + +void P2_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Make sure only called once. + + LFA_FileRef oldFile = 0; + std::string filePath, tempPath; + + // Update the internal legacy XML tree if we have one, and set the digest in the XMP. + // *** This is just a minimal prototype. + + bool updateLegacyXML = false; + + if ( this->clipMetadata != 0 ) { + + XMP_Assert ( this->expat != 0 ); + + bool xmpFound; + std::string xmpValue; + XML_Node * xmlNode; + + xmpFound = this->xmpObj.GetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", 0, &xmpValue, 0 ); + + if ( xmpFound ) { + + xmlNode = this->ForceChildElement ( this->clipContent, "ClipName", 3 ); + + if ( xmpValue != xmlNode->GetLeafContentValue() ) { + xmlNode->SetLeafContentValue ( xmpValue.c_str() ); + updateLegacyXML = true; + } + + } + + xmpFound = this->xmpObj.GetArrayItem ( kXMP_NS_DC, "creator", 1, &xmpValue, 0 ); + + if ( xmpFound ) { + xmlNode = this->ForceChildElement ( this->clipMetadata, "Access", 3 ); + xmlNode = this->ForceChildElement ( xmlNode, "Creator", 4 ); + if ( xmpValue != xmlNode->GetLeafContentValue() ) { + xmlNode->SetLeafContentValue ( xmpValue.c_str() ); + updateLegacyXML = true; + } + } + + } + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "P2", newDigest.c_str(), kXMP_DeleteExisting ); + + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() ); + + // Update the legacy XML file if necessary. + + if ( updateLegacyXML ) { + + std::string legacyXML; + this->expat->tree.Serialize ( &legacyXML ); + + this->MakeClipFilePath ( &filePath, ".XML" ); + oldFile = LFA_Open ( filePath.c_str(), 'w' ); + + if ( oldFile == 0 ) { + + // The XML does not exist yet. + + this->MakeClipFilePath ( &filePath, ".XML" ); + oldFile = LFA_Create ( filePath.c_str() ); + if ( oldFile == 0 ) XMP_Throw ( "Failure creating P2 legacy XML file", kXMPErr_ExternalFailure ); + LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() ); + LFA_Close ( oldFile ); + + } else if ( ! doSafeUpdate ) { + + // Over write the existing XML file. + + LFA_Seek ( oldFile, 0, SEEK_SET ); + LFA_Truncate ( oldFile, 0 ); + LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() ); + LFA_Close ( oldFile ); + + } else { + + // Do a safe update. + + // *** We really need an LFA_SwapFiles utility. + + this->MakeClipFilePath ( &filePath, ".XML" ); + + CreateTempFile ( filePath, &tempPath ); + LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' ); + LFA_Write ( tempFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() ); + LFA_Close ( tempFile ); + + LFA_Close ( oldFile ); + LFA_Delete ( filePath.c_str() ); + LFA_Rename ( tempPath.c_str(), filePath.c_str() ); + + } + + } + + // Update the XMP file. + + oldFile = this->parent->fileRef; + + if ( oldFile == 0 ) { + + // The XMP does not exist yet. + + this->MakeClipFilePath ( &filePath, ".XMP" ); + oldFile = LFA_Create ( filePath.c_str() ); + if ( oldFile == 0 ) XMP_Throw ( "Failure creating P2 XMP file", kXMPErr_ExternalFailure ); + LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( oldFile ); + + } else if ( ! doSafeUpdate ) { + + // Over write the existing XMP file. + + LFA_Seek ( oldFile, 0, SEEK_SET ); + LFA_Truncate ( oldFile, 0 ); + LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( oldFile ); + + } else { + + // Do a safe update. + + // *** We really need an LFA_SwapFiles utility. + + this->MakeClipFilePath ( &filePath, ".XMP" ); + + CreateTempFile ( filePath, &tempPath ); + LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' ); + LFA_Write ( tempFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( tempFile ); + + LFA_Close ( oldFile ); + LFA_Delete ( filePath.c_str() ); + LFA_Rename ( tempPath.c_str(), filePath.c_str() ); + + } + + this->parent->fileRef = 0; + +} // P2_MetaHandler::UpdateFile + +// ================================================================================================= +// P2_MetaHandler::WriteFile +// ========================= + +void P2_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) +{ + + // ! WriteFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "P2_MetaHandler::WriteFile should not be called", kXMPErr_InternalFailure ); + +} // P2_MetaHandler::WriteFile + +// ================================================================================================= diff --git a/source/XMPFiles/FileHandlers/P2_Handler.hpp b/source/XMPFiles/FileHandlers/P2_Handler.hpp new file mode 100644 index 0000000..8581fbd --- /dev/null +++ b/source/XMPFiles/FileHandlers/P2_Handler.hpp @@ -0,0 +1,106 @@ +#ifndef __P2_Handler_hpp__ +#define __P2_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. + +#include "XMPFiles_Impl.hpp" + +#include "ExpatAdapter.hpp" + +#include "MD5.h" + +// ================================================================================================= +/// \file P2_Handler.hpp +/// \brief Folder format handler for P2. +/// +/// This header ... +/// +// ================================================================================================= + +// *** Could derive from Basic_Handler - buffer file tail in a temp file. + +extern XMPFileHandler * P2_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool P2_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kP2_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_UsesSidecarXMP | + kXMPFiles_FolderBasedFormat); + +class P2_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files. + { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); }; + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); + + P2_MetaHandler ( XMPFiles * _parent ); + virtual ~P2_MetaHandler(); + +private: + + P2_MetaHandler() : expat(0), clipMetadata(0), clipContent(0) {}; // Hidden on purpose. + + void MakeClipFilePath ( std::string * path, XMP_StringPtr suffix ); + void MakeLegacyDigest ( std::string * digestStr ); + void CleanupLegacyXML(); + + void DigestLegacyItem ( MD5_CTX & md5Context, XML_NodePtr legacyContext, XMP_StringPtr legacyPropName ); + void DigestLegacyRelations ( MD5_CTX & md5Context ); + + void SetXMPPropertyFromLegacyXML ( bool digestFound, + XML_NodePtr legacyContext, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr legacyPropName, + bool isLocalized ); + + void SetRelationsFromLegacyXML ( bool digestFound ); + void SetAudioInfoFromLegacyXML ( bool digestFound ); + void SetVideoInfoFromLegacyXML ( bool digestFound ); + void SetDurationFromLegacyXML ( bool digestFound ); + + void SetVideoFrameInfoFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound ); + void SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound ); + + XML_Node * ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, int indent = 0 ); + + std::string rootPath, clipName, p2NS, defaultNS; + + ExpatAdapter * expat; + XML_Node * clipMetadata; // ! Don't delete, points into the Expat tree. + XML_Node * clipContent; // ! Don't delete, points into the Expat tree. + +}; // P2_MetaHandler + +// ================================================================================================= + +#endif /* __P2_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/PNG_Handler.cpp b/source/XMPFiles/FileHandlers/PNG_Handler.cpp index 44a08dd..7e82eaa 100644 --- a/source/XMPFiles/FileHandlers/PNG_Handler.cpp +++ b/source/XMPFiles/FileHandlers/PNG_Handler.cpp @@ -138,7 +138,7 @@ void PNG_MetaHandler::ProcessXMP() XMP_Assert ( this->containsXMP ); XMP_StringPtr packetStr = this->xmpPacket.c_str(); - XMP_StringLen packetLen = this->xmpPacket.size(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); @@ -160,7 +160,7 @@ void PNG_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( doSafeUpdate ) XMP_Throw ( "PNG_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); XMP_StringPtr packetStr = xmpPacket.c_str(); - XMP_StringLen packetLen = xmpPacket.size(); + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); if ( packetLen == 0 ) return; LFA_FileRef fileRef(this->parent->fileRef); @@ -227,7 +227,7 @@ void PNG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sou if (PNG_Support::CheckIHDRChunkHeader(chunk)) { XMP_StringPtr packetStr = xmpPacket.c_str(); - XMP_StringLen packetLen = xmpPacket.size(); + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); PNG_Support::WriteXMPChunk(destRef, packetLen, packetStr ); } diff --git a/source/XMPFiles/FileHandlers/PSD_Handler.cpp b/source/XMPFiles/FileHandlers/PSD_Handler.cpp index 6d9523e..600be29 100644 --- a/source/XMPFiles/FileHandlers/PSD_Handler.cpp +++ b/source/XMPFiles/FileHandlers/PSD_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 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 @@ -249,7 +249,12 @@ void PSD_MetaHandler::ProcessXMP() this->iptcMgr = new IPTC_Reader(); this->exifMgr = new TIFF_MemoryReader(); } else { - this->iptcMgr = new IPTC_Writer(); + #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->exifMgr = new TIFF_FileWriter(); } @@ -287,7 +292,7 @@ void PSD_MetaHandler::ProcessXMP() XMP_Assert ( this->containsXMP ); // Common code takes care of packetInfo.charForm, .padSize, and .writeable. XMP_StringPtr packetStr = this->xmpPacket.c_str(); - XMP_StringLen packetLen = this->xmpPacket.size(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); try { this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); } catch ( ... ) { @@ -327,14 +332,20 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; - bool doInPlace = (oldPacketOffset != 0) && (oldPacketLength != 0); // ! Has old packet and new packet fits. - if ( doInPlace && (this->psirMgr.IsLegacyChanged()) ) doInPlace = false; + bool doInPlace = (this->xmpPacket.size() <= (size_t)this->packetInfo.length); + if ( this->psirMgr.IsLegacyChanged() ) doInPlace = false; if ( doInPlace ) { #if GatherPerformanceData sAPIPerf->back().extraInfo += ", PSD in-place update"; #endif + + if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { + // They ought to match, cheap to be sure. + size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); + this->xmpPacket.append ( extraSpace, ' ' ); + } LFA_FileRef liveFile = this->parent->fileRef; @@ -343,7 +354,7 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) // 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(), this->xmpPacket.size() ); + LFA_Write ( liveFile, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); } else { @@ -418,10 +429,10 @@ void PSD_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sou this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); this->packetInfo.offset = kXMPFiles_UnknownOffset; - this->packetInfo.length = this->xmpPacket.size(); - this->packetInfo.padSize = GetPacketPadSize ( this->xmpPacket.c_str(), this->xmpPacket.size() ); + this->packetInfo.length = (XMP_StringLen)this->xmpPacket.size(); + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); - this->psirMgr.SetImgRsrc ( kPSIR_XMP, this->xmpPacket.c_str(), this->xmpPacket.size() ); + 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). diff --git a/source/XMPFiles/FileHandlers/PostScript_Handler.cpp b/source/XMPFiles/FileHandlers/PostScript_Handler.cpp index 316b37c..2b74646 100644 --- a/source/XMPFiles/FileHandlers/PostScript_Handler.cpp +++ b/source/XMPFiles/FileHandlers/PostScript_Handler.cpp @@ -23,7 +23,7 @@ using namespace std; // ================================================================================================= static const char * kPSFileTag = "%!PS-Adobe-"; -static const int kPSFileTagLen = strlen ( kPSFileTag ); +static const size_t kPSFileTagLen = strlen ( kPSFileTag ); // ================================================================================================= // PostScript_MetaHandlerCTor @@ -193,10 +193,10 @@ PostScript_MetaHandler::~PostScript_MetaHandler() // the XMP marker is found, look for the MainFirst/MainLast/NoMain options. static const char * kPSContainsXMPString = "%ADO_ContainsXMP:"; -static const int kPSContainsXMPLength = strlen ( kPSContainsXMPString ); +static const size_t kPSContainsXMPLength = strlen ( kPSContainsXMPString ); static const char * kPSEndCommentString = "%%EndComments"; // ! Assumed shorter than kPSContainsXMPString. -static const int kPSEndCommentLength = strlen ( kPSEndCommentString ); +static const size_t kPSEndCommentLength = strlen ( kPSEndCommentString ); int PostScript_MetaHandler::FindPostScriptHint() { @@ -430,7 +430,7 @@ bool PostScript_MetaHandler::FindLastPacket() LFA_Seek ( fileRef, 0, SEEK_SET ); // Seek back to the beginning of the file. - for ( bufPos = 0; bufPos < fileLen; bufPos += bufLen ) { + for ( bufPos = 0; bufPos < (size_t)fileLen; bufPos += bufLen ) { if ( checkAbort && abortProc(abortArg) ) { XMP_Throw ( "PostScript_MetaHandler::FindLastPacket - User abort", kXMPErr_UserAbort ); } diff --git a/source/XMPFiles/FileHandlers/SWF_Handler.cpp b/source/XMPFiles/FileHandlers/SWF_Handler.cpp new file mode 100644 index 0000000..100f79e --- /dev/null +++ b/source/XMPFiles/FileHandlers/SWF_Handler.cpp @@ -0,0 +1,397 @@ +// ================================================================================================= +// 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 "SWF_Handler.hpp" + +#include "SWF_Support.hpp" + + +using namespace std; + +// ================================================================================================= +/// \file SWF_Handler.hpp +/// \brief File format handler for SWF. +/// +/// This handler ... +/// +// ================================================================================================= + +// ================================================================================================= +// SWF_MetaHandlerCTor +// ==================== + +XMPFileHandler * SWF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new SWF_MetaHandler ( parent ); + +} // SWF_MetaHandlerCTor + +// ================================================================================================= +// SWF_CheckFormat +// =============== + +bool SWF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ) +{ + IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent); + XMP_Assert ( format == kXMP_SWFFile ); + + IOBuffer ioBuf; + + LFA_Seek ( fileRef, 0, SEEK_SET ); + if ( ! CheckFileSpace ( fileRef, &ioBuf, SWF_SIGNATURE_LEN ) ) return false; + + if ( !(CheckBytes ( ioBuf.ptr, SWF_F_SIGNATURE_DATA, SWF_SIGNATURE_LEN ) || + CheckBytes(ioBuf.ptr, SWF_C_SIGNATURE_DATA, SWF_SIGNATURE_LEN)) ) + return false; + + return true; + +} // SWF_CheckFormat + +// ================================================================================================= +// SWF_MetaHandler::SWF_MetaHandler +// ================================== + +SWF_MetaHandler::SWF_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; + this->handlerFlags = kSWF_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} + +// ================================================================================================= +// SWF_MetaHandler::~SWF_MetaHandler +// =================================== + +SWF_MetaHandler::~SWF_MetaHandler() +{ +} + +// ================================================================================================= +// SWF_MetaHandler::CacheFileData +// =============================== + +void SWF_MetaHandler::CacheFileData() +{ + + this->containsXMP = false; + + LFA_FileRef fileRef ( this->parent->fileRef ); + if ( fileRef == 0) return; + + SWF_Support::FileInfo fileInfo(fileRef, this->parent->filePath); + IO::InputStream * is = NULL; + if(fileInfo.IsCompressed()) + { + XMP_Uns32 fileSize = fileInfo.GetSize(); + is = new IO::ZIP::DeflateInputStream(fileRef, fileSize); + + IO::ZIP::DeflateInputStream * in = dynamic_cast<IO::ZIP::DeflateInputStream*>(is); + in->Skip(SWF_COMPRESSION_BEGIN, IO::ZIP::DEFLATE_NO); + } + else + { + is = new IO::FileInputStream(fileRef); + is->Skip(SWF_COMPRESSION_BEGIN); + } + + SWF_Support::TagState tagState; + //important flag to stop iteration over all tags when xmp flag within FileAttributeTag isn't set + tagState.cachingFile = true; + + long numTags = SWF_Support::OpenSWF ( is, tagState ); + + is->Close(); + delete is; + + if ( numTags == 0 ) return; + + if (tagState.hasXMP && tagState.xmpLen != 0) + { + this->xmpPacket.assign(tagState.xmpPacket); + this->containsXMP = true; + } + else + { + // no XMP + } + + +} // 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 +// ============================ +// +// Process the raw XMP and legacy metadata that was previously cached. + +void SWF_MetaHandler::ProcessXMP() +{ + + this->processedXMP = true; // Make sure we only come through here once. + + // Process the XMP packet. + + if ( ! this->xmpPacket.empty() ) { + + XMP_Assert ( this->containsXMP ); + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); + + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + + this->containsXMP = true; + + } + +} // SWF_MetaHandler::ProcessXMP + + +// ================================================================================================= +// XMPFileHandler::GetSerializeOptions +// =================================== +// +// Override default implementation to ensure omitting xmp wrapper +// +XMP_OptionBits SWF_MetaHandler::GetSerializeOptions() +{ + return (kXMP_OmitPacketWrapper | kXMP_OmitAllFormatting | kXMP_OmitXMPMetaElement); +} // XMPFileHandler::GetSerializeOptions + + +// ================================================================================================= +// SWF_MetaHandler::UpdateFile +// ============================ + +void SWF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + bool updated = false; + + if ( ! this->needsUpdate ) return; + if ( doSafeUpdate ) XMP_Throw ( "SWF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); + + LFA_FileRef sourceRef = this->parent->fileRef; + std::string sourcePath = this->parent->filePath; + + SWF_Support::FileInfo fileInfo(sourceRef, sourcePath); + + if(fileInfo.IsCompressed()) + sourceRef = fileInfo.Decompress(); + + IO::InputStream * is = new IO::FileInputStream(sourceRef); + //processing SWF starts after byte SWF_COMPRESSION_BEGIN - currently 8 bytes + is->Skip(SWF_COMPRESSION_BEGIN); + + SWF_Support::TagState tagState; + + long numTags = SWF_Support::OpenSWF ( is, tagState ); + + //clean objects + is->Close(); + delete is; + + bool foundTag = false; + SWF_Support::TailBufferDef tailBuffer; + + + //find end position to measure tail buffer + tailBuffer.tailEndPosition = LFA_Seek(sourceRef, 0, SEEK_END); + + SWF_Support::TagIterator curPos = tagState.tags.begin(); + SWF_Support::TagIterator endPos = tagState.tags.end(); + + for( ;(curPos != endPos) && ! foundTag; ++curPos) + { + SWF_Support::TagData tag = *curPos; + + //write XMP Tag at the beginning of the file + if(! tagState.hasXMP && ! tagState.hasFileAttrTag) + { + tailBuffer.tailStartPosition = tag.pos; + tailBuffer.writePosition = tag.pos; + foundTag = true; + } + //update existing XMP Tag + if(tagState.hasXMP && (tagState.xmpTag.pos == tag.pos)) + { + ++curPos; + SWF_Support::TagData nextTag = *curPos; + tailBuffer.tailStartPosition = nextTag.pos; + tailBuffer.writePosition = tagState.xmpTag.pos; + foundTag = true; + } + //write XMP Tag after FileAttribute Tag + else if(! tagState.hasXMP && (tag.id == SWF_TAG_ID_FILEATTRIBUTES)) + { + ++curPos; + SWF_Support::TagData nextTag = *curPos; + tailBuffer.tailStartPosition = nextTag.pos; + tailBuffer.writePosition = nextTag.pos; + foundTag = true; + } + } + + //cache tail of file + XMP_Assert(tailBuffer.tailEndPosition > tailBuffer.tailStartPosition); + XMP_Uns32 tailSize = tailBuffer.GetTailSize(); + XMP_Uns8 * buffer = new XMP_Uns8[tailSize]; + SWF_Support::ReadBuffer(sourceRef, tailBuffer.tailStartPosition, tailSize, buffer); + + //write new XMP packet + XMP_StringPtr packetStr = xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); + + LFA_Seek(sourceRef, tailBuffer.writePosition, SEEK_SET); + SWF_Support::WriteXMPTag(sourceRef, packetLen, packetStr); + + // truncate to minimal size + LFA_Truncate(sourceRef, LFA_Tell(sourceRef)); + + //move tail of the file + LFA_Write(sourceRef, buffer, tailSize); + + //cleaning buffer + delete [] buffer; + + //update FileAttribute Tag if exists + if(tagState.hasFileAttrTag) + SWF_Support::UpdateFileAttrTag(sourceRef, tagState.fileAttrTag, tagState); + + + //update File Size + SWF_Support::UpdateHeader(sourceRef); + + //compress file after writing XMP + if(fileInfo.IsCompressed()) + { + fileInfo.Compress(sourceRef, this->parent->fileRef); + fileInfo.Clean(); + } + + + if ( ! updated )return; // If there's an error writing the chunk, bail. + + this->needsUpdate = false; + +} // SWF_MetaHandler::UpdateFile + +// ================================================================================================= +// SWF_MetaHandler::WriteFile +// =========================== + +void SWF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) +{ + + LFA_FileRef destRef = this->parent->fileRef; + + SWF_Support::TagState tagState; + + LFA_FileRef origRef(destRef); + std::string updatePath; + + + SWF_Support::FileInfo fileInfo(sourceRef, sourcePath); + + if(fileInfo.IsCompressed()) + { + sourceRef = fileInfo.Decompress(); + CreateTempFile ( sourcePath, &updatePath, kCopyMacRsrc ); + destRef = LFA_Open ( updatePath.c_str(), 'w' ); + } + + IO::InputStream * is = NULL; + + is = new IO::FileInputStream(sourceRef); + is->Skip(SWF_COMPRESSION_BEGIN); + + long numTags = SWF_Support::OpenSWF( is, tagState ); + + is->Close(); + delete is; + + if ( numTags == 0 ) return; + + LFA_Truncate(destRef, 0); + SWF_Support::CopyHeader(sourceRef, destRef, tagState); + + SWF_Support::TagIterator curPos = tagState.tags.begin(); + SWF_Support::TagIterator endPos = tagState.tags.end(); + + XMP_StringPtr packetStr = xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); + + bool copying = true; + bool isXMPTagWritten = false; + + for (; (curPos != endPos); ++curPos) + { + SWF_Support::TagData tag = *curPos; + + // write XMP tag right after FileAttribute Tag if no XMP tag exists + if (! tagState.hasXMP && (tag.id == SWF_TAG_ID_FILEATTRIBUTES)) + SWF_Support::WriteXMPTag(destRef, packetLen, packetStr ); + + //no FileAttribute Tag and no XMP tag write XMP Tag at the beginning of the file + if(!tagState.hasXMP && !tagState.hasFileAttrTag && ! isXMPTagWritten) + { + isXMPTagWritten = true; + SWF_Support::WriteXMPTag(destRef, packetLen, packetStr ); + } + + // write XMP tag where old XMP exists + if(tagState.hasXMP && (tag.pos == tagState.xmpTag.pos)) + { + copying = false; + SWF_Support::WriteXMPTag(destRef, packetLen, packetStr ); + } + + // copy any other chunk + if(copying) + SWF_Support::CopyTag(sourceRef, destRef, tag); + + copying = true; + } + + // update FileAttribute Tag in new file + if(tagState.hasFileAttrTag) + SWF_Support::UpdateFileAttrTag(destRef, tagState.fileAttrTag, tagState); + + + // update file header by measuring new file size + SWF_Support::UpdateHeader(origRef); + + //compress re-written file + if(fileInfo.IsCompressed()) + { + fileInfo.Compress(destRef, origRef); + fileInfo.Clean(); + LFA_Close(destRef); + LFA_Delete(updatePath.c_str()); + } + + + + +} // SWF_MetaHandler::WriteFile + diff --git a/source/XMPFiles/FileHandlers/SWF_Handler.hpp b/source/XMPFiles/FileHandlers/SWF_Handler.hpp new file mode 100644 index 0000000..511ddd6 --- /dev/null +++ b/source/XMPFiles/FileHandlers/SWF_Handler.hpp @@ -0,0 +1,65 @@ +#ifndef __SWF_Handler_hpp__ +#define __SWF_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 "XMP_Environment.h" // ! This must be the first include. + +#include "XMPFiles_Impl.hpp" + +//#include "SWF_Support.hpp" + +// ================================================================================================= +/// \file SWF_Handler.hpp +/// \brief File format handler for SWF. +/// +/// This header ... +/// +// ================================================================================================= + +// *** Could derive from Basic_Handler - buffer file tail in a temp file. + +extern XMPFileHandler* SWF_MetaHandlerCTor ( XMPFiles* parent ); + +extern bool SWF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles* parent ); + +static const XMP_OptionBits kSWF_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket + ); + +class SWF_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessTNail(); + void ProcessXMP(); + + XMP_OptionBits GetSerializeOptions(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string& sourcePath ); + + SWF_MetaHandler ( XMPFiles* parent ); + virtual ~SWF_MetaHandler(); + + + +}; // SWF_MetaHandler + +// ================================================================================================= + +#endif /* __SWF_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/Scanner_Handler.cpp b/source/XMPFiles/FileHandlers/Scanner_Handler.cpp index 8d704a6..4959ca0 100644 --- a/source/XMPFiles/FileHandlers/Scanner_Handler.cpp +++ b/source/XMPFiles/FileHandlers/Scanner_Handler.cpp @@ -76,7 +76,7 @@ PickMainPacket ( std::vector<CandidateInfo>& candidates, bool beLenient ) int main = -1; // Assume the worst. XMP_OptionBits options; - int metaCount = candidates.size(); + int metaCount = (int)candidates.size(); if ( metaCount == 0 ) return -1; if ( metaCount == 1 ) return 0; @@ -281,10 +281,10 @@ void Scanner_MetaHandler::CacheFileData() try { for ( bufPos = 0; bufPos < snips[pkt].fLength; bufPos += bufLen ) { bufLen = kBufferSize; - if ( (bufPos + bufLen) > snips[pkt].fLength ) bufLen = size_t ( snips[pkt].fLength - bufPos ); - (void) LFA_Read ( fileRef, buffer, bufLen, kLFA_RequireAll ); + if ( (bufPos + bufLen) > (size_t)snips[pkt].fLength ) bufLen = size_t ( snips[pkt].fLength - bufPos ); + (void) LFA_Read ( fileRef, buffer, (XMP_Int32)bufLen, kLFA_RequireAll ); xmpPacket.append ( (const char *)buffer, bufLen ); - newMeta->ParseFromBuffer ( (char *)buffer, bufLen, kXMP_ParseMoreBuffers ); + newMeta->ParseFromBuffer ( (char *)buffer, (XMP_StringLen)bufLen, kXMP_ParseMoreBuffers ); } newMeta->ParseFromBuffer ( 0, 0, kXMP_NoOptions ); } catch ( ... ) { diff --git a/source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp b/source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp new file mode 100644 index 0000000..2dca918 --- /dev/null +++ b/source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp @@ -0,0 +1,782 @@ +// ================================================================================================= +// 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 "SonyHDV_Handler.hpp" + +#include "MD5.h" + +#if XMP_WinBuild + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +using namespace std; + +// ================================================================================================= +/// \file SonyHDV_Handler.cpp +/// \brief Folder format handler for Sony HDV. +/// +/// This handler is for the Sony HDV video format. This is a pseudo-package, visible files but with +/// a very well-defined layout and naming rules. +/// +/// A typical Sony HDV layout looks like: +/// +/// .../MyMovie/ +/// VIDEO/ +/// HDV/ +/// 00_0001_2007-08-06_165555.IDX +/// 00_0001_2007-08-06_165555.M2T +/// 00_0001_2007-08-06_171740.M2T +/// 00_0001_2007-08-06_171740.M2T.ese +/// tracks.dat +/// +/// The logical clip name can be "00_0001" or "00_0001_" plus anything. We'll find the .IDX file, +/// which defines the existence of the clip. Full file names as input will pull out the camera/clip +/// parts and match in the same way. The .XMP file will use the date/time suffix from the .IDX file. +// ================================================================================================= + +// ================================================================================================= +// SonyHDV_CheckFormat +// =================== +// +// This version does fairly simple checks. The top level folder (.../MyMovie) must contain the +// VIDEO/HVR subtree. The HVR folder must contain a .IDX file for the desired clip. The name checks +// are case insensitive. +// +// The state of the string parameters depends on the form of the path passed by the client. If the +// client passed a logical clip path, like ".../MyMovie/00_0001", the parameters are: +// rootPath - ".../MyMovie" +// gpName - empty +// parentName - empty +// leafName - "00_0001" +// +// If the client passed a full file path, like ".../MyMovie/VIDEO/HVR/00_0001_2007-08-06_165555.M2T", +// they are: +// rootPath - ".../MyMovie" +// gpName - "VIDEO" +// parentName - "HVR" +// leafName - "00_0001_2007-08-06_165555.M2T" +// +// The logical clip name can be short like "00_0001", or long like "00_0001_2007-08-06_165555". We +// only key off of the portion before a second underscore. + +// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +bool SonyHDV_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ) +{ + // Do some basic checks on the root path and component names. + + if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty. + + std::string tempPath = rootPath; + tempPath += kDirChar; + tempPath += "VIDEO"; + + if ( gpName.empty() ) { + // This is the logical clip path case. Look for VIDEO/HVR subtree. + if ( GetChildMode ( tempPath, "HVR" ) != kFMode_IsFolder ) return false; + } else { + // This is the existing file case. Check the parent and grandparent names. + if ( (gpName != "VIDEO") || (parentName != "HVR") ) return false; + } + + // Look for the clip's .IDX file. If found use that as the full clip name. + + tempPath += kDirChar; + tempPath += "HVR"; + + std::string clipName = leafName; + int usCount = 0; + size_t i, limit = leafName.size(); + for ( i = 0; i < limit; ++i ) { + if ( clipName[i] == '_' ) { + ++usCount; + if ( usCount == 2 ) break; + } + } + if ( i < limit ) clipName.erase ( i ); + clipName += '_'; // Make sure a final '_' is there for the search comparisons. + + XMP_FolderInfo folderInfo; + std::string childName; + bool found = false; + + folderInfo.Open ( tempPath.c_str() ); + while ( (! found) && folderInfo.GetNextChild ( &childName ) ) { + size_t childLen = childName.size(); + if ( childLen < 4 ) continue; + MakeUpperCase ( &childName ); + if ( childName.compare ( childLen-4, 4, ".IDX" ) != 0 ) continue; + if ( childName.compare ( 0, clipName.size(), clipName ) == 0 ) { + found = true; + clipName = childName; + clipName.erase ( childLen-4 ); + } + } + if ( ! found ) return false; + + + // Disabled until Sony HDV clip spanning is supported. + // Since segments of spanned clips are currently considered separate entities, + // information such as frame count needs to be considered on a per segment basis. JPM + 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. + + 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. + + return true; + +} // SonyHDV_CheckFormat + +// ================================================================================================= +// ReadIDXFile +// =========== + +#define ExtractTimeCodeByte(ch,mask) ( (((ch & mask) >> 4) * 10) + (ch & 0xF) ) + +static bool ReadIDXFile ( const std::string& idxPath, + const std::string& clipName, + SXMPMeta* xmpObj, + bool& containsXMP, + MD5_CTX* md5Context, + bool digestFound ) +{ + bool result = true; + containsXMP = false; + + if ( clipName.size() != 25 ) return false; + + try { + + + AutoFile idxFile; + idxFile.fileRef = LFA_Open ( idxPath.c_str(), 'r' ); + if ( idxFile.fileRef == 0 ) return false; // The open failed. + + struct SHDV_HeaderBlock + { + char mHeader[8]; + unsigned char mValidFlag; + unsigned char mReserved; + unsigned char mECCTB; + unsigned char mSignalMode; + unsigned char mFileThousands; + unsigned char mFileHundreds; + unsigned char mFileTens; + unsigned char mFileUnits; + }; + + SHDV_HeaderBlock hdvHeaderBlock; + memset ( &hdvHeaderBlock, 0, sizeof(SHDV_HeaderBlock) ); + + LFA_Read ( idxFile.fileRef, hdvHeaderBlock.mHeader, 8 ); + LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mValidFlag, 1 ); + LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mReserved, 1 ); + LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mECCTB, 1 ); + LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mSignalMode, 1 ); + LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mFileThousands, 1 ); + LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mFileHundreds, 1 ); + LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mFileTens, 1 ); + LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mFileUnits, 1 ); + + const int fileCount = (hdvHeaderBlock.mFileThousands - '0') * 1000 + + (hdvHeaderBlock.mFileHundreds - '0') * 100 + + (hdvHeaderBlock.mFileTens - '0') * 10 + + (hdvHeaderBlock.mFileUnits - '0'); + + // Read file info block. + struct SHDV_FileBlock + { + char mDT[2]; + unsigned char mFileNameYear; + unsigned char mFileNameMonth; + unsigned char mFileNameDay; + unsigned char mFileNameHour; + unsigned char mFileNameMinute; + unsigned char mFileNameSecond; + unsigned char mStartTimeCode[4]; + unsigned char mTotalFrame[4]; + }; + + SHDV_FileBlock hdvFileBlock; + memset ( &hdvFileBlock, 0, sizeof(SHDV_FileBlock) ); + + char filenameBuffer[256]; + std::string fileDateAndTime = clipName.substr(8); + + bool foundFileBlock = false; + + for ( int i=0; ((i < fileCount) && (! foundFileBlock)); ++i ) { + + LFA_Read ( idxFile.fileRef, hdvFileBlock.mDT, 2 ); + LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameYear, 1 ); + LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameMonth, 1 ); + LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameDay, 1 ); + LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameHour, 1 ); + LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameMinute, 1 ); + LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameSecond, 1 ); + LFA_Read ( idxFile.fileRef, &hdvFileBlock.mStartTimeCode, 4 ); + LFA_Read ( idxFile.fileRef, &hdvFileBlock.mTotalFrame, 4 ); + + // Compose file name we expect from file contents and break out on match. + sprintf ( filenameBuffer, "%02d-%02d-%02d_%02d%02d%02d", + hdvFileBlock.mFileNameYear + 2000, + hdvFileBlock.mFileNameMonth, + hdvFileBlock.mFileNameDay, + hdvFileBlock.mFileNameHour, + hdvFileBlock.mFileNameMinute, + hdvFileBlock.mFileNameSecond ); + + foundFileBlock = (fileDateAndTime==filenameBuffer); + + } + + LFA_Close ( idxFile.fileRef ); + idxFile.fileRef = 0; + + if ( ! foundFileBlock ) return false; + + // If digest calculation requested, calculate it and return. + if ( md5Context != 0 ) { + MD5Update ( md5Context, (XMP_Uns8*)(&hdvHeaderBlock), sizeof(SHDV_HeaderBlock) ); + MD5Update ( md5Context, (XMP_Uns8*)(&hdvFileBlock), sizeof(SHDV_FileBlock) ); + } + + // The xmpObj parameter must be provided in order to extract XMP + if ( xmpObj == 0 ) return (md5Context != 0); + + // Standard def? + const bool isSD = ((hdvHeaderBlock.mSignalMode == 0x80) || (hdvHeaderBlock.mSignalMode == 0)); + + // Progressive vs interlaced extracted from high bit of ECCTB byte + const bool clipIsProgressive = ((hdvHeaderBlock.mECCTB & 0x80) != 0); + + // Lowest three bits contain frame rate information + const int sfr = (hdvHeaderBlock.mECCTB & 7) + (clipIsProgressive ? 0 : 8); + + // Sample scale and sample size. + int clipSampleScale = 0; + int clipSampleSize = 0; + std::string frameRate; + + // Frame rate + switch ( sfr ) { + case 0 : break; // Not valid in spec, but it's happening in test files. + case 1 : clipSampleScale = 24000; clipSampleSize = 1001; frameRate = "23.98p"; break; + case 3 : clipSampleScale = 25; clipSampleSize = 1; frameRate = "25p"; break; + case 4 : clipSampleScale = 30000; clipSampleSize = 1001; frameRate = "29.97p"; break; + case 11 : clipSampleScale = 25; clipSampleSize = 1; frameRate = "50i"; break; + case 12 : clipSampleScale = 30000; clipSampleSize = 1001; frameRate = "59.94i"; break; + } + + containsXMP = true; + + // Frame size and PAR for HD (not clear on SD yet). + std::string xmpString; + XMP_StringPtr xmpValue = 0; + + if ( ! isSD ) { + + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoFrameSize" )) ) { + + xmpValue = "1440"; + xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "w", &xmpString, 0 ); + if ( xmpString != xmpValue ) { + xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", xmpValue, 0 ); + } + + xmpValue = "1080"; + xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "h", &xmpString, 0 ); + if ( xmpString != xmpValue ) { + xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", xmpValue, 0 ); + } + + xmpValue = "pixels"; + xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "unit", &xmpString, 0 ); + if ( xmpString != xmpValue ) { + xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", xmpValue, 0 ); + } + } + + xmpValue = "4/3"; + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoPixelAspectRatio" )) ) { + xmpObj->SetProperty ( kXMP_NS_DM, "videoPixelAspectRatio", xmpValue, kXMP_DeleteExisting ); + } + + } + + // Sample size and scale. + if ( clipSampleScale != 0 ) { + + char buffer[255]; + + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimeScale" )) ) { + sprintf(buffer, "%d", clipSampleScale); + xmpValue = buffer; + xmpObj->SetProperty ( kXMP_NS_DM, "startTimeScale", xmpValue, kXMP_DeleteExisting ); + } + + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimeSampleSize" )) ) { + sprintf(buffer, "%d", clipSampleSize); + xmpValue = buffer; + xmpObj->SetProperty ( kXMP_NS_DM, "startTimeSampleSize", xmpValue, kXMP_DeleteExisting ); + } + + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) { + + const int frameCount = (hdvFileBlock.mTotalFrame[0] << 24) + (hdvFileBlock.mTotalFrame[1] << 16) + + (hdvFileBlock.mTotalFrame[2] << 8) + hdvFileBlock.mTotalFrame[3]; + + sprintf ( buffer, "%d", frameCount ); + xmpValue = buffer; + xmpObj->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", xmpValue, 0 ); + + sprintf ( buffer, "%d/%d", clipSampleSize, clipSampleScale ); + xmpValue = buffer; + xmpObj->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", xmpValue, 0 ); + + } + + } + + // Time Code. + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimecode" )) ) { + + if ( (clipSampleScale != 0) && (clipSampleSize != 0) ) { + + const bool dropFrame = ( (0x40 & hdvFileBlock.mStartTimeCode[0]) != 0 ) && ( sfr == 4 || sfr == 12 ); + const char chDF = dropFrame ? ';' : ':'; + const int tcFrames = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[0], 0x30 ); + const int tcSeconds = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[1], 0x70 ); + const int tcMinutes = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[2], 0x70 ); + const int tcHours = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[3], 0x30 ); + + // HH:MM:SS:FF or HH;MM;SS;FF + char timecode[256]; + sprintf ( timecode, "%02d%c%02d%c%02d%c%02d", tcHours, chDF, tcMinutes, chDF, tcSeconds, chDF, tcFrames ); + std::string sonyTimeString = timecode; + + xmpObj->GetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", &xmpString, 0 ); + if ( xmpString != sonyTimeString ) { + + xmpObj->SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", sonyTimeString, 0 ); + + std::string timeFormat; + if ( clipSampleSize == 1 ) { + + // 24, 25, 40, 50, 60 + switch ( clipSampleScale ) { + case 24 : timeFormat = "24"; break; + case 25 : timeFormat = "25"; break; + case 50 : timeFormat = "50"; break; + default : XMP_Assert ( false ); + } + + timeFormat += "Timecode"; + + } else { + + // 23.976, 29.97, 59.94 + XMP_Assert ( clipSampleSize == 1001 ); + switch ( clipSampleScale ) { + case 24000 : timeFormat = "23976"; break; + case 30000 : timeFormat = "2997"; break; + case 60000 : timeFormat = "5994"; break; + default : XMP_Assert( false ); break; + } + + timeFormat += dropFrame ? "DropTimecode" : "NonDropTimecode"; + + } + + xmpObj->SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", timeFormat, 0 ); + + } + + } + + } + + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "CreateDate" )) ) { + + // Clip has date and time in the case of DT (otherwise date and time haven't been set). + bool clipHasDate = ((hdvFileBlock.mDT[0] == 'D') && (hdvFileBlock.mDT[1] == 'T')); + + // Creation date + if ( clipHasDate ) { + + // YYYY-MM-DDThh:mm:ssZ + char date[256]; + sprintf ( date, "%4d-%02d-%02dT%02d:%02d:%02dZ", + hdvFileBlock.mFileNameYear + 2000, + hdvFileBlock.mFileNameMonth, + hdvFileBlock.mFileNameDay, + hdvFileBlock.mFileNameHour, + hdvFileBlock.mFileNameMinute, + hdvFileBlock.mFileNameSecond ); + + XMP_StringPtr xmpDate = date; + xmpObj->SetProperty ( kXMP_NS_XMP, "CreateDate", xmpDate, kXMP_DeleteExisting ); + + } + + } + + // Frame rate. + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoFrameRate" )) ) { + + if ( frameRate.size() != 0 ) { + xmpString = frameRate; + xmpObj->SetProperty ( kXMP_NS_DM, "videoFrameRate", xmpString, kXMP_DeleteExisting ); + } + + } + + } catch ( ... ) { + + result = false; + + } + + return result; + +} // ReadIDXFile + +// ================================================================================================= +// SonyHDV_MetaHandlerCTor +// ======================= + +XMPFileHandler * SonyHDV_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new SonyHDV_MetaHandler ( parent ); + +} // SonyHDV_MetaHandlerCTor + +// ================================================================================================= +// SonyHDV_MetaHandler::SonyHDV_MetaHandler +// ======================================== + +SonyHDV_MetaHandler::SonyHDV_MetaHandler ( XMPFiles * _parent ) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kSonyHDV_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + // Extract the root path and clip name. + + XMP_Assert ( this->parent->handlerTemp != 0 ); + + this->rootPath.assign ( (char*) this->parent->handlerTemp ); + free ( this->parent->handlerTemp ); + this->parent->handlerTemp = 0; + + SplitLeafName ( &this->rootPath, &this->clipName ); + +} // SonyHDV_MetaHandler::SonyHDV_MetaHandler + +// ================================================================================================= +// SonyHDV_MetaHandler::~SonyHDV_MetaHandler +// ========================================= + +SonyHDV_MetaHandler::~SonyHDV_MetaHandler() +{ + + if ( this->parent->handlerTemp != 0 ) { + free ( this->parent->handlerTemp ); + this->parent->handlerTemp = 0; + } + +} // SonyHDV_MetaHandler::~SonyHDV_MetaHandler + +// ================================================================================================= +// SonyHDV_MetaHandler::MakeClipFilePath +// ===================================== + +void SonyHDV_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix ) +{ + + *path = this->rootPath; + *path += kDirChar; + *path += "VIDEO"; + *path += kDirChar; + *path += "HVR"; + *path += kDirChar; + *path += this->clipName; + *path += suffix; + +} // SonyHDV_MetaHandler::MakeClipFilePath + +// ================================================================================================= +// SonyHDV_MetaHandler::MakeIndexFilePath +// ====================================== + +bool SonyHDV_MetaHandler::MakeIndexFilePath ( std::string& idxPath, const std::string& rootPath, const std::string& leafName ) +{ + std::string tempPath; + tempPath = rootPath; + tempPath += kDirChar; + tempPath += "VIDEO"; + tempPath += kDirChar; + tempPath += "HVR"; + + idxPath = tempPath; + idxPath += kDirChar; + idxPath += leafName; + idxPath += ".IDX"; + + // Default case + if ( GetFileMode ( idxPath.c_str() ) == kFMode_IsFile ) return true; + + // Spanned clip case + + // Scanning code taken from SonyHDV_CheckFormat + // Can be isolated to a separate function. + + std::string clipName = leafName; + int usCount = 0; + size_t i, limit = leafName.size(); + + for ( i = 0; i < limit; ++i ) { + if ( clipName[i] == '_' ) { + ++usCount; + if ( usCount == 2 ) break; + } + } + + if ( i < limit ) clipName.erase ( i ); + clipName += '_'; // Make sure a final '_' is there for the search comparisons. + + XMP_FolderInfo folderInfo; + std::string childName; + bool found = false; + + folderInfo.Open ( tempPath.c_str() ); + while ( (! found) && folderInfo.GetNextChild ( &childName ) ) { + size_t childLen = childName.size(); + if ( childLen < 4 ) continue; + MakeUpperCase ( &childName ); + if ( childName.compare ( childLen-4, 4, ".IDX" ) != 0 ) continue; + if ( childName.compare ( 0, clipName.size(), clipName ) == 0 ) { + found = true; + clipName = childName; + clipName.erase ( childLen-4 ); + } + } + if ( ! found ) return false; + + idxPath = tempPath; + idxPath += kDirChar; + idxPath += clipName; + idxPath += ".IDX"; + + return true; +} + +// ================================================================================================= +// SonyHDV_MetaHandler::MakeLegacyDigest +// ===================================== + +#define kHexDigits "0123456789ABCDEF" + +void SonyHDV_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + std::string idxPath; + if ( ! this->MakeIndexFilePath ( idxPath, this->rootPath, this->clipName ) ) return; + + MD5_CTX context; + unsigned char digestBin [16]; + bool dummy = false; + MD5Init ( &context ); + ReadIDXFile ( idxPath, this->clipName, 0, dummy, &context, false ); + MD5Final ( digestBin, &context ); + + char buffer [40]; + for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digestBin[in]; + buffer[out] = kHexDigits [ byte >> 4 ]; + buffer[out+1] = kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + digestStr->erase(); + digestStr->append ( buffer, 32 ); + +} // MakeLegacyDigest + +// ================================================================================================= +// SonyHDV_MetaHandler::CacheFileData +// ================================== + +void SonyHDV_MetaHandler::CacheFileData() +{ + XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + + // See if the clip's .XMP file exists. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, ".XMP" ); + if ( GetFileMode ( xmpPath.c_str() ) != kFMode_IsFile ) return; // No XMP. + + // Read the entire .XMP file. + + char openMode = 'r'; + if ( this->parent->openFlags & kXMPFiles_OpenForUpdate ) openMode = 'w'; + + LFA_FileRef xmpFile = LFA_Open ( xmpPath.c_str(), openMode ); + if ( xmpFile == 0 ) return; // The open failed. + + XMP_Int64 xmpLen = LFA_Measure ( xmpFile ); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "SonyHDV XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.reserve ( (size_t)xmpLen ); + this->xmpPacket.append ( (size_t)xmpLen, ' ' ); + + XMP_Int32 ioCount = LFA_Read ( xmpFile, (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen, kLFA_RequireAll ); + XMP_Assert ( ioCount == xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + XMP_Assert ( this->parent->fileRef == 0 ); + if ( openMode == 'r' ) { + LFA_Close ( xmpFile ); + } else { + this->parent->fileRef = xmpFile; + } + + this->containsXMP = true; + +} // SonyHDV_MetaHandler::CacheFileData + +// ================================================================================================= +// SonyHDV_MetaHandler::ProcessXMP +// =============================== + +void SonyHDV_MetaHandler::ProcessXMP() +{ + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + // Check the legacy digest. + std::string oldDigest, newDigest; + bool digestFound; + digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "SonyHDV", &oldDigest, 0 ); + if ( digestFound ) { + this->MakeLegacyDigest ( &newDigest ); + if ( oldDigest == newDigest ) return; + } + + // Read the IDX legacy. + std::string idxPath; + if ( ! this->MakeIndexFilePath ( idxPath, this->rootPath, this->clipName ) ) return; + ReadIDXFile ( idxPath, this->clipName, &this->xmpObj, this->containsXMP, 0, digestFound ); + +} // SonyHDV_MetaHandler::ProcessXMP + +// ================================================================================================= +// SonyHDV_MetaHandler::UpdateFile +// =============================== +// +// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here. + +void SonyHDV_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Make sure only called once. + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "SonyHDV", newDigest.c_str(), kXMP_DeleteExisting ); + + LFA_FileRef oldFile = this->parent->fileRef; + + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() ); + + if ( oldFile == 0 ) { + + // The XMP does not exist yet. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, ".XMP" ); + + LFA_FileRef xmpFile = LFA_Create ( xmpPath.c_str() ); + if ( xmpFile == 0 ) XMP_Throw ( "Failure creating SonyHDV XMP file", kXMPErr_ExternalFailure ); + LFA_Write ( xmpFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( xmpFile ); + + } else if ( ! doSafeUpdate ) { + + // Over write the existing XMP file. + + LFA_Seek ( oldFile, 0, SEEK_SET ); + LFA_Truncate ( oldFile, 0 ); + LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( oldFile ); + + } else { + + // Do a safe update. + + // *** We really need an LFA_SwapFiles utility. + + std::string xmpPath, tempPath; + + this->MakeClipFilePath ( &xmpPath, ".XMP" ); + + CreateTempFile ( xmpPath, &tempPath ); + LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' ); + LFA_Write ( tempFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( tempFile ); + + LFA_Close ( oldFile ); + LFA_Delete ( xmpPath.c_str() ); + LFA_Rename ( tempPath.c_str(), xmpPath.c_str() ); + + } + + this->parent->fileRef = 0; + +} // SonyHDV_MetaHandler::UpdateFile + +// ================================================================================================= +// SonyHDV_MetaHandler::WriteFile +// ============================== + +void SonyHDV_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) +{ + + // ! WriteFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "SonyHDV_MetaHandler::WriteFile should not be called", kXMPErr_InternalFailure ); + +} // SonyHDV_MetaHandler::WriteFile + +// ================================================================================================= diff --git a/source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp b/source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp new file mode 100644 index 0000000..a69f0dc --- /dev/null +++ b/source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp @@ -0,0 +1,77 @@ +#ifndef __SonyHDV_Handler_hpp__ +#define __SonyHDV_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. + +#include "XMPFiles_Impl.hpp" + +#include "ExpatAdapter.hpp" + +// ================================================================================================= +/// \file SonyHDV_Handler.hpp +/// \brief Folder format handler for SonyHDV. +/// +/// This header ... +/// +// ================================================================================================= + +extern XMPFileHandler * SonyHDV_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool SonyHDV_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kSonyHDV_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + +class SonyHDV_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files. + { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); }; + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); + + SonyHDV_MetaHandler ( XMPFiles * _parent ); + virtual ~SonyHDV_MetaHandler(); + +private: + + SonyHDV_MetaHandler() {}; // Hidden on purpose. + + void MakeClipFilePath ( std::string * path, XMP_StringPtr suffix ); + bool MakeIndexFilePath ( std::string& idxPath, const std::string& rootPath, const std::string& leafName ); + void MakeLegacyDigest ( std::string * digestStr ); + + std::string rootPath, clipName; + +}; // SonyHDV_MetaHandler + +// ================================================================================================= + +#endif /* __SonyHDV_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/TIFF_Handler.cpp b/source/XMPFiles/FileHandlers/TIFF_Handler.cpp index e63b28c..a12e718 100644 --- a/source/XMPFiles/FileHandlers/TIFF_Handler.cpp +++ b/source/XMPFiles/FileHandlers/TIFF_Handler.cpp @@ -1,6 +1,6 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 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 @@ -120,6 +120,22 @@ void TIFF_MetaHandler::CacheFileData() this->tiffMgr.ParseFileStream ( fileRef ); + TIFF_Manager::TagInfo dngInfo; + if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, &dngInfo ) ) { + + // Reject DNG files that are version 2.0 or beyond, this is being written at the time of + // DNG version 1.2. The DNG team says it is OK to use 2.0, not strictly 1.2. Use the + // DNGBackwardVersion if it is present, else the DNGVersion. Note that the version value is + // supposed to be type BYTE, so the file order is always essentially big endian. + + XMP_Uns8 majorVersion = *((XMP_Uns8*)dngInfo.dataPtr); // Start with DNGVersion. + if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGBackwardVersion, &dngInfo ) ) { + majorVersion = *((XMP_Uns8*)dngInfo.dataPtr); // Use DNGBackwardVersion if possible. + } + if ( majorVersion > 1 ) XMP_Throw ( "DNG version beyond 1.x", kXMPErr_BadTIFF ); + + } + TIFF_Manager::TagInfo xmpInfo; bool found = this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, &xmpInfo ); @@ -198,7 +214,12 @@ void TIFF_MetaHandler::ProcessXMP() this->iptcMgr = new IPTC_Reader(); } else { this->psirMgr = new PSIR_FileWriter(); - this->iptcMgr = new IPTC_Writer(); + #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 } TIFF_Manager & tiff = this->tiffMgr; // Give the compiler help in recognizing non-aliases. @@ -264,7 +285,7 @@ void TIFF_MetaHandler::ProcessXMP() XMP_Assert ( this->containsXMP ); // Common code takes care of packetInfo.charForm, .padSize, and .writeable. XMP_StringPtr packetStr = this->xmpPacket.c_str(); - XMP_StringLen packetLen = this->xmpPacket.size(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); try { this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); } catch ( ... ) { @@ -312,21 +333,27 @@ void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; - bool doInPlace = (oldPacketOffset != 0) && (oldPacketLength != 0); // ! Has old packet and new packet fits. - if ( doInPlace && (this->tiffMgr.IsLegacyChanged()) ) doInPlace = false; + bool doInPlace = (this->xmpPacket.size() <= (size_t)this->packetInfo.length); + if ( this->tiffMgr.IsLegacyChanged() ) doInPlace = false; if ( doInPlace ) { #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(), this->xmpPacket.size() ); + LFA_Write ( liveFile, this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() ); } else { @@ -337,10 +364,10 @@ void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) // 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 = this->xmpPacket.size(); - this->packetInfo.padSize = GetPacketPadSize ( this->xmpPacket.c_str(), this->xmpPacket.size() ); + this->packetInfo.length = (XMP_Int32)this->xmpPacket.size(); + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); - this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, this->xmpPacket.size(), this->xmpPacket.c_str() ); + this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, (XMP_Uns32)this->xmpPacket.size(), this->xmpPacket.c_str() ); this->tiffMgr.UpdateFileStream ( destRef ); diff --git a/source/XMPFiles/FileHandlers/UCF_Handler.cpp b/source/XMPFiles/FileHandlers/UCF_Handler.cpp new file mode 100644 index 0000000..e9c2219 --- /dev/null +++ b/source/XMPFiles/FileHandlers/UCF_Handler.cpp @@ -0,0 +1,846 @@ +// ================================================================================================= +// 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. +// =============================================================================================== + +#if XMP_WinBuild + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +#include "UCF_Handler.hpp" + +#include "zlib.h" + +#include <time.h> + +#ifdef DYNAMIC_CRC_TABLE + #error "unexpectedly DYNAMIC_CRC_TABLE defined." + //Must implement get_crc_table prior to any multi-threading (see notes there) +#endif + +using namespace std; + +// ================================================================================================= +/// \file UCF_Handler.cpp +/// \brief UCF handler class +// ================================================================================================= +const XMP_Uns16 xmpFilenameLen = 21; +const char* xmpFilename = "META-INF/metadata.xml"; + +// ================================================================================================= +// UCF_MetaHandlerCTor +// ==================== +XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new UCF_MetaHandler ( parent ); +} // UCF_MetaHandlerCTor + +// ================================================================================================= +// UCF_CheckFormat +// ================ +// * lenght must at least be 114 bytes +// * first bytes must be \x50\x4B\x03\x04 for *any* zip file +// * at offset 30 it must spell "mimetype" + +#define MIN_UCF_LENGTH 114 +// zip minimum considerations: +// the shortest legal zip is 100 byte: +// 30+1* bytes file header +//+ 0 byte content file (uncompressed) +//+ 46+1* bytes central directory file header +//+ 22 byte end of central directory record +//------- +//100 bytes +// +//1 byte is the shortest legal filename. anything below is no valid zip. +// +//==> the mandatory+first "mimetype" content file has a filename length of 8 bytes, +// thus even if empty (arguably incorrect but tolerable), +// the shortest legal UCF is 114 bytes (30 + 8 + 0 + 46 + 8 + 22 ) +// anything below is with certainty not a valid ucf. + +bool UCF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ) +{ + // *not* using buffer functionality here, all we need + // to detect UCF securely is in the first 38 bytes... + IgnoreParam(filePath); IgnoreParam(parent); //suppress warnings + XMP_Assert ( format == kXMP_UCFFile ); //standard assert + + XMP_Uns8 buffer[MIN_UCF_LENGTH]; + + LFA_Seek ( fileRef, 0, SEEK_SET ); + if ( MIN_UCF_LENGTH != LFA_Read ( fileRef, buffer, MIN_UCF_LENGTH) ) //NO requireall (->no throw), just return false + return false; + if ( !CheckBytes ( &buffer[0], "\x50\x4B\x03\x04", 4 ) ) // "PK 03 04" + return false; + // UCF spec says: there must be a content file mimetype, and be first and be uncompressed... + if ( !CheckBytes ( &buffer[30], "mimetype", 8 ) ) + return false; + + ////////////////////////////////////////////////////////////////////////////// + //figure out mimetype, decide on writeability + // grab mimetype + LFA_Seek( fileRef, 18, SEEK_SET ); + XMP_Uns32 mimeLength = LFA_ReadUns32_LE ( fileRef ); + XMP_Uns32 mimeCompressedLength = LFA_ReadUns32_LE ( fileRef ); // must be same since uncompressed + + XMP_Validate( mimeLength == mimeCompressedLength, + "mimetype compressed and uncompressed length differ", + kXMPErr_BadFileFormat ); + + XMP_Validate( mimeLength != 0, "0-byte mimetype", kXMPErr_BadFileFormat ); + + // determine writability based on mimetype + LFA_Seek( fileRef, 30 + 8, SEEK_SET ); + char* mimetype = new char[ mimeLength + 1 ]; + LFA_Read( fileRef, mimetype, mimeLength, true ); + mimetype[mimeLength] = '\0'; + + bool okMimetype; + + // be lenient on extraneous CR (0xA) [non-XMP bug #16980028] + if ( mimeLength > 0 ) //avoid potential crash (will properly fail below anyhow) + if ( mimetype[mimeLength-1] == 0xA ) + mimetype[mimeLength-1] = '\0'; + + if ( + XMP_LitMatch( mimetype, "application/vnd.adobe.xfl" ) || //Flash Diesel team + XMP_LitMatch( mimetype, "application/vnd.adobe.xfl+zip") || //Flash Diesel team + XMP_LitMatch( mimetype, "application/vnd.adobe.x-mars" ) || //Mars plugin(labs only), Acrobat8 + XMP_LitMatch( mimetype, "application/vnd.adobe.pdfxml" ) || //Mars plugin(labs only), Acrobat 9 + XMP_LitMatch( mimetype, "vnd.adobe.x-asnd" ) || //Adobe Sound Document (Soundbooth Team) + XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-idml-package" ) ) //inCopy (inDesign) IDML Document + + // *** ==> unknown are also treated as not acceptable + okMimetype = true; + else + okMimetype = false; + + // not accepted (neither read nor write + //.air - Adobe Air Files + //application/vnd.adobe.air-application-installer-package+zip + //.airi - temporary Adobe Air Files + //application/vnd.adobe.air-application-intermediate-package+zip + + delete mimetype; + return okMimetype; + +} // UCF_CheckFormat + +// ================================================================================================= +// UCF_MetaHandler::UCF_MetaHandler +// ================================== + +UCF_MetaHandler::UCF_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; + this->handlerFlags = kUCF_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; +} // UCF_MetaHandler::UCF_MetaHandler + +// ================================================================================================= +// UCF_MetaHandler::~UCF_MetaHandler +// ===================================== + +UCF_MetaHandler::~UCF_MetaHandler() +{ + // nothing +} + +// ================================================================================================= +// UCF_MetaHandler::CacheFileData +// =============================== +// +void UCF_MetaHandler::CacheFileData() +{ + //*** abort procedures + this->containsXMP = false; //assume no XMP for now (beware of exceptions...) + LFA_FileRef file = this->parent->fileRef; + XMP_PacketInfo &packetInfo = this->packetInfo; + + // clear file positioning info --------------------------------------------------- + b=0;b2=0;x=0;x2=0;cd=0;cd2=0;cdx=0;cdx2=0;h=0;h2=0,fl=0;f2l=0; + al=0;bl=0;xl=0;x2l=0;cdl=0;cd2l=0;cdxl=0;cdx2l=0;hl=0,z=0,z2=0,z2l=0; + numCF=0;numCF2=0; + wasCompressed = false; + + // ------------------------------------------------------------------------------- + fl=LFA_Measure( file ); + if ( fl < MIN_UCF_LENGTH ) XMP_Throw("file too short, can't be correct UCF",kXMPErr_Unimplemented); + + ////////////////////////////////////////////////////////////////////////////// + // find central directory before optional comment + // things have to go bottom-up, since description headers are allowed in UCF + // "scan backwards" until feasible field found (plus sig sanity check) + // OS buffering should be smart enough, so not doing anything on top + // plus almost all comments will be zero or rather short + + //no need to check anything but the 21 chars of "METADATA-INF/metadata.xml" + char filenameToTest[22]; + filenameToTest[21]='\0'; + + XMP_Int32 zipCommentLen = 0; + for ( ; zipCommentLen <= EndOfDirectory::COMMENT_MAX; zipCommentLen++ ) + { + LFA_Seek( file, -zipCommentLen -2, SEEK_END ); + if ( LFA_ReadUns16_LE( file ) == zipCommentLen ) //found it? + { + //double check, might just look like comment length (actually be 'evil' comment) + LFA_Seek( file , - EndOfDirectory::FIXED_SIZE, SEEK_CUR ); + if ( LFA_ReadUns32_LE( file ) == EndOfDirectory::ID ) break; //heureka, directory ID + // 'else': pretend nothing happended, just go on + } + } + //was it a break or just not found ? + if ( zipCommentLen > EndOfDirectory::COMMENT_MAX ) XMP_Throw( "zip broken near end or invalid comment" , kXMPErr_BadFileFormat ); + + //////////////////////////////////////////////////////////////////////////// + //read central directory + hl = zipCommentLen + EndOfDirectory::FIXED_SIZE; + h = fl - hl; + LFA_Seek( file , h , SEEK_SET ); + + if ( LFA_ReadUns32_LE( file ) != EndOfDirectory::ID ) + XMP_Throw("directory header id not found. or broken comment",kXMPErr_BadFileFormat); + if ( LFA_ReadUns16_LE( file ) != 0 ) + XMP_Throw("UCF must be 'first' zip volume",kXMPErr_BadFileFormat); + if ( LFA_ReadUns16_LE( file ) != 0 ) + XMP_Throw("UCF must be single-volume zip",kXMPErr_BadFileFormat); + + numCF = LFA_ReadUns16_LE( file ); //number of content files + if ( numCF != LFA_ReadUns16_LE( file ) ) + XMP_Throw( "per volume and total number of dirs differ" , kXMPErr_BadFileFormat ); + cdl = LFA_ReadUns32_LE( file ); + cd = LFA_ReadUns32_LE( file ); + LFA_Seek( file, 2,SEEK_CUR ); //skip comment len, needed since next LFA is SEEK_CUR ! + + ////////////////////////////////////////////////////////////////////////////// + // check for zip64-end-of-CD-locator/ zip64-end-of-CD + // to to central directory + if ( cd == 0xffffffff ) + { // deal with zip 64, otherwise continue + XMP_Int64 tmp = LFA_Seek( file, + - EndOfDirectory::FIXED_SIZE - Zip64Locator::TOTAL_SIZE, + SEEK_CUR ); //go to begining of zip64 locator + //relative movement , absolute would imho only require another -zipCommentLen + + if ( Zip64Locator::ID == LFA_ReadUns32_LE(file) ) // prevent 'coincidental length' ffffffff + { + XMP_Validate( 0 == LFA_ReadUns32_LE(file), + "zip64 CD disk must be 0", kXMPErr_BadFileFormat ); + + z = LFA_ReadUns64_LE(file); + XMP_Validate( z < 0xffffffffffffLL, "file in terrabyte range?", kXMPErr_BadFileFormat ); // 3* ffff, sanity test + + XMP_Uns32 totalNumOfDisks = LFA_ReadUns32_LE(file); + /* tolerated while pkglib bug #1742179 */ + XMP_Validate( totalNumOfDisks == 0 || totalNumOfDisks == 1, + "zip64 total num of disks must be 0", kXMPErr_BadFileFormat ); + + /////////////////////////////////////////////// + /// on to end-of-CD itself + LFA_Seek( file, z, SEEK_SET ); + XMP_Validate( Zip64EndOfDirectory::ID == LFA_ReadUns32_LE(file), + "invalid zip64 end of CD sig", kXMPErr_BadFileFormat ); + + XMP_Int64 sizeOfZip64EOD = LFA_ReadUns64_LE(file); + LFA_Seek(file, 12, SEEK_CUR ); + //yes twice "total" and "per disk" + XMP_Int64 tmp64 = LFA_ReadUns64_LE(file); + XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (1)", kXMPErr_BadFileFormat ); + tmp64 = LFA_ReadUns64_LE(file); + XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (2)", kXMPErr_BadFileFormat ); + // cd length verification + tmp64 = LFA_ReadUns64_LE(file); + XMP_Validate( tmp64 == cdl, "CD length differs in zip64", kXMPErr_BadFileFormat ); + + cd = LFA_ReadUns64_LE(file); // wipe out invalid 0xffffffff with the real thing + //ignoring "extensible data sector (would need fullLength - fixed length) for now + } + } // of zip64 fork + ///////////////////////////////////////////////////////////////////////////// + // parse central directory + // 'foundXMP' <=> cdx != 0 + + LFA_Seek( file, cd, SEEK_SET ); + XMP_Int64 cdx_suspect=0; + XMP_Int64 cdxl_suspect=0; + CDFileHeader curCDHeader; + + for ( XMP_Uns16 entryNum=1 ; entryNum <= numCF ; entryNum++ ) + { + cdx_suspect = LFA_Tell( file ); //just suspect for now + curCDHeader.read( file ); + + if ( GetUns32LE( &curCDHeader.fields[CDFileHeader::o_sig] ) != 0x02014b50 ) + XMP_Throw("&invalid file header",kXMPErr_BadFileFormat); + + cdxl_suspect = curCDHeader.FIXED_SIZE + + GetUns16LE(&curCDHeader.fields[CDFileHeader::o_fileNameLength]) + + GetUns16LE(&curCDHeader.fields[CDFileHeader::o_extraFieldLength]) + + GetUns16LE(&curCDHeader.fields[CDFileHeader::o_commentLength]); + + // we only look 21 characters, that's META-INF/metadata.xml, no \0 attached + if ( curCDHeader.filenameLen == xmpFilenameLen /*21*/ ) + if( XMP_LitNMatch( curCDHeader.filename , "META-INF/metadata.xml", 21 ) ) + { + cdx = cdx_suspect; + cdxl = cdxl_suspect; + break; + } + //hop to next + LFA_Seek( file, cdx_suspect + cdxl_suspect , SEEK_SET ); + } //for-loop, iterating *all* central directory headers (also beyond found) + + if ( !cdx ) // not found xmp + { + // b and bl remain 0, x and xl remain 0 + // ==> a is everything before directory + al = cd; + return; + } + + // from here is if-found-only + ////////////////////////////////////////////////////////////////////////////// + //CD values needed, most serve counter-validation purposes (below) only + // read whole object (incl. all 3 fields) again properly + // to get extra Fields, etc + LFA_Seek( file, cdx, SEEK_SET ); + xmpCDHeader.read( file ); + + XMP_Validate( xmpFilenameLen == GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_fileNameLength]), + "content file length not ok", kXMPErr_BadFileFormat ); + + XMP_Uns16 CD_compression = GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_compression] ); + XMP_Validate(( CD_compression == 0 || CD_compression == 0x08), + "illegal compression, must be flate or none", kXMPErr_BadFileFormat ); + XMP_Uns16 CD_flags = GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_flags] ); + XMP_Uns32 CD_crc = GetUns32LE( &xmpCDHeader.fields[CDFileHeader::o_crc32] ); + + // parse (actual, non-CD!) file header //////////////////////////////////////////////// + x = xmpCDHeader.offsetLocalHeader; + LFA_Seek( file , x ,SEEK_SET); + xmpFileHeader.read( file ); + xl = xmpFileHeader.sizeHeader() + xmpCDHeader.sizeCompressed; + + //values needed + XMP_Uns16 fileNameLength = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_fileNameLength] ); + XMP_Uns16 extraFieldLength = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_extraFieldLength] ); + XMP_Uns16 compression = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_compression] ); + XMP_Uns32 sig = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sig] ); + XMP_Uns16 flags = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_flags] ); + XMP_Uns32 sizeCompressed = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeCompressed] ); + XMP_Uns32 sizeUncompressed = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] ); + XMP_Uns32 crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] ); + + // check filename + XMP_Validate( fileNameLength == 21, "filename size contradiction" , kXMPErr_BadFileFormat ); + XMP_Enforce ( xmpFileHeader.filename != 0 ); + XMP_Validate( !memcmp( "META-INF/metadata.xml", xmpFileHeader.filename , xmpFilenameLen ) , "filename is cf header is not META-INF/metadata.xml" , kXMPErr_BadFileFormat ); + + // deal with data descriptor if needed + if ( flags & FileHeader::kdataDescriptorFlag ) + { + if ( sizeCompressed!=0 || sizeUncompressed!=0 || crc!=0 ) XMP_Throw("data descriptor must mean 3x zero",kXMPErr_BadFileFormat); + LFA_Seek( file, xmpCDHeader.sizeCompressed + fileNameLength + xmpCDHeader.extraFieldLen, SEEK_CUR); //skip actual data to get to descriptor + crc = LFA_ReadUns32_LE( file ); + if ( crc == 0x08074b50 ) //data descriptor may or may not have signature (see spec) + { + crc = LFA_ReadUns32_LE( file ); //if it does, re-read + } + sizeCompressed = LFA_ReadUns32_LE( file ); + sizeUncompressed = LFA_ReadUns32_LE( file ); + // *** cater for zip64 plus 'streamed' data-descriptor stuff + } + + // more integrity checks (post data descriptor handling) + if ( sig != 0x04034b50 ) XMP_Throw("invalid content file header",kXMPErr_BadFileFormat); + if ( compression != CD_compression ) XMP_Throw("compression contradiction",kXMPErr_BadFileFormat); + if ( sizeUncompressed != xmpCDHeader.sizeUncompressed ) XMP_Throw("contradicting uncompressed lengths",kXMPErr_BadFileFormat); + if ( sizeCompressed != xmpCDHeader.sizeCompressed ) XMP_Throw("contradicting compressed lengths",kXMPErr_BadFileFormat); + if ( sizeUncompressed == 0 ) XMP_Throw("0-byte uncompressed size", kXMPErr_BadFileFormat ); + + //////////////////////////////////////////////////////////////////// + // packet Info + this->packetInfo.charForm = stdCharForm; + this->packetInfo.writeable = false; + this->packetInfo.offset = kXMPFiles_UnknownOffset; // checksum!, hide position to not give funny ideas + this->packetInfo.length = kXMPFiles_UnknownLength; + + //////////////////////////////////////////////////////////////////// + // prepare packet (compressed or not) + this->xmpPacket.erase(); + this->xmpPacket.reserve( sizeUncompressed ); + this->xmpPacket.append( sizeUncompressed, ' ' ); + XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() ); // only set after reserving the space! + + // go to packet offset + LFA_Seek ( file, x + xmpFileHeader.FIXED_SIZE + fileNameLength + extraFieldLength , SEEK_SET); + + // compression fork -------------------------------------------------- + switch (compression) + { + case 0x8: // FLATE + { + wasCompressed = true; + XMP_Uns32 bytesRead = 0; + XMP_Uns32 bytesWritten = 0; // for writing into packetString + const unsigned int CHUNK = 16384; + + int ret; + unsigned int have; //added type + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + // does need this intermediate stage, no direct compressio to packetStr possible, + // since also partially filled buffers must be picked up. That's how it works. + // in addition: internal zlib variables might have 16 bit limits... + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + + /* must use windowBits = -15, for raw inflate, no zlib header */ + ret = inflateInit2(&strm,-MAX_WBITS); + + if (ret != Z_OK) + XMP_Throw("zlib error ",kXMPErr_ExternalFailure); + + /* decompress until deflate stream ends or end of file */ + do { + // must take care here not to read too much, thus whichever is smaller: + XMP_Int32 bytesRemaining = sizeCompressed - bytesRead; + if ( (XMP_Int32)CHUNK < bytesRemaining ) bytesRemaining = (XMP_Int32)CHUNK; + strm.avail_in=LFA_Read( file , in , bytesRemaining , true ); + bytesRead += strm.avail_in; // NB: avail_in is "unsigned_int", so might be 16 bit (not harmfull) + + if (strm.avail_in == 0) break; + strm.next_in = in; + + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + XMP_Assert( ret != Z_STREAM_ERROR ); /* state not clobbered */ + switch (ret) + { + case Z_NEED_DICT: + (void)inflateEnd(&strm); + XMP_Throw("zlib error: Z_NEED_DICT",kXMPErr_ExternalFailure); + case Z_DATA_ERROR: + (void)inflateEnd(&strm); + XMP_Throw("zlib error: Z_DATA_ERROR",kXMPErr_ExternalFailure); + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + XMP_Throw("zlib error: Z_MEM_ERROR",kXMPErr_ExternalFailure); + } + + have = CHUNK - strm.avail_out; + memcpy( (unsigned char*) packetStr + bytesWritten , out , have ); + bytesWritten += have; + + } while (strm.avail_out == 0); + + /* it's done when inflate() says it's done */ + } while (ret != Z_STREAM_END); + + /* clean up and return */ + (void)inflateEnd(&strm); + if (ret != Z_STREAM_END) + XMP_Throw("zlib error ",kXMPErr_ExternalFailure); + break; + } + case 0x0: // no compression - read directly into the right place + { + wasCompressed = false; + XMP_Enforce( LFA_Read ( file, (char*)packetStr, sizeUncompressed, kLFA_RequireAll ) ); + break; + } + default: + { + XMP_Throw("illegal zip compression method (not none, not flate)",kXMPErr_BadFileFormat); + } + } + this->containsXMP = true; // do this last, after all possible failure/execptions +} + + +// ================================================================================================= +// UCF_MetaHandler::ProcessXMP +// ============================ + +void UCF_MetaHandler::ProcessXMP() +{ + // we have no legacy, CacheFileData did all that was needed + // ==> default implementation is fine + XMPFileHandler::ProcessXMP(); +} + +// ================================================================================================= +// UCF_MetaHandler::UpdateFile +// ============================= + +// TODO: xmp packet with data descriptor + +void UCF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + //sanity + XMP_Enforce( (x!=0) == (cdx!=0) ); + if (!cdx) + xmpCDHeader.setXMPFilename(); //if new, set filename (impacts length, thus before computation) + if ( ! this->needsUpdate ) + return; + + // *** + if ( doSafeUpdate ) XMP_Throw ( "UCF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); + + LFA_FileRef file = this->parent->fileRef; + + // final may mean compressed or not, whatever is to-be-embedded + uncomprPacketStr = xmpPacket.c_str(); + uncomprPacketLen = (XMP_StringLen) xmpPacket.size(); + finalPacketStr = uncomprPacketStr; // will be overriden if compressedXMP==true + finalPacketLen = uncomprPacketLen; + std::string compressedPacket; // moot if non-compressed, still here for scope reasons (having to keep a .c_str() alive) + + if ( !x ) // if new XMP... + { + xmpFileHeader.clear(); + xmpFileHeader.setXMPFilename(); + // ZIP64 TODO: extra Fields, impact on cdxl2 and x2l + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + // COMPRESSION DECISION + + // for large files compression is bad: + // a) size of XMP becomes irrelevant on large files ==> why worry over compression ? + // b) more importantly: no such thing as padding possible, compression == ever changing sizes + // => never in-place rewrites, *ugly* performance impact on large files + inPlacePossible = false; //assume for now + + if ( !x ) // no prior XMP? -> decide on filesize + compressXMP = ( fl > 1024*50 /* 100 kB */ ) ? false : true; + else + compressXMP = wasCompressed; // don't change a thing + + if ( !wasCompressed && !compressXMP && + ( GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] ) == uncomprPacketLen )) + { + inPlacePossible = true; + } + //////////////////////////////////////////////////////////////////////////////////////////////// + // COMPRESS XMP + if ( compressXMP ) + { + const unsigned int CHUNK = 16384; + int ret, flush; + unsigned int have; + z_stream strm; + unsigned char out[CHUNK]; + + /* allocate deflate state */ + strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; + if ( deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8 /*memlevel*/, Z_DEFAULT_STRATEGY) ) + XMP_Throw("zlib error ",kXMPErr_ExternalFailure); + + //write at once, since we got it in mem anyway: + strm.avail_in = uncomprPacketLen; + flush = Z_FINISH; // that's all, folks + strm.next_in = (unsigned char*) uncomprPacketStr; + + do { + strm.avail_out = CHUNK; + strm.next_out = out; + + ret = deflate(&strm, flush); /* no bad return value (!=0 acceptable) */ + XMP_Enforce(ret != Z_STREAM_ERROR); /* state not clobbered */ + //fwrite(buffer,size,count,file) + have = CHUNK - strm.avail_out; + compressedPacket.append( (const char*) out, have); + } while (strm.avail_out == 0); + + if (ret != Z_STREAM_END) + XMP_Throw("zlib stream incomplete ",kXMPErr_ExternalFailure); + XMP_Enforce(strm.avail_in == 0); // all input will be used + (void)deflateEnd(&strm); //clean up (do prior to checks) + + finalPacketStr = compressedPacket.c_str(); + finalPacketLen = (XMP_StringLen)compressedPacket.size(); + } + + PutUns32LE ( uncomprPacketLen, &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] ); + PutUns32LE ( finalPacketLen, &xmpFileHeader.fields[FileHeader::o_sizeCompressed] ); + PutUns16LE ( compressXMP ? 8:0, &xmpFileHeader.fields[FileHeader::o_compression] ); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // CRC (always of uncompressed data) + XMP_Uns32 crc = crc32( 0 , (Bytef*)uncomprPacketStr, uncomprPacketLen ); + PutUns32LE( crc, &xmpFileHeader.fields[FileHeader::o_crc32] ); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // TIME calculation for timestamp + // will be applied both to xmp content file and CD header + XMP_Uns16 lastModTime, lastModDate; + XMP_DateTime time; + SXMPUtils::CurrentDateTime( &time ); + + if ( (time.year - 1900) < 80) + { + lastModTime = 0; // 1.1.1980 00:00h + lastModDate = 21; + } + + // typedef unsigned short ush; //2 bytes + lastModDate = (XMP_Uns16) (((time.year) - 1980 ) << 9 | ((time.month) << 5) | time.day); + lastModTime = ((XMP_Uns16)time.hour << 11) | ((XMP_Uns16)time.minute << 5) | ((XMP_Uns16)time.second >> 1); + + PutUns16LE ( lastModDate, &xmpFileHeader.fields[FileHeader::o_lastmodDate] ); + PutUns16LE ( lastModTime, &xmpFileHeader.fields[FileHeader::o_lastmodTime] ); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // adjustments depending on 4GB Border, + // decisions on in-place update + // so far only z, zl have been determined + + // Zip64 related assurances, see (15) + XMP_Enforce(!z2); + XMP_Enforce(h+hl == fl ); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // COMPUTE MISSING VARIABLES + // A - based on xmp existence + // + // already known: x, xl, cd + // most left side vars, + // + // finalPacketStr, finalPacketLen + + if ( x ) // previous xmp? + { + al = x; + b = x + xl; + bl = cd - b; + } + else + { + al = cd; + //b,bl left at zero + } + + if ( inPlacePossible ) + { // leave xmp right after A + x2 = al; + x2l = xmpFileHeader.sizeTotalCF(); //COULDDO: assert (x2l == xl) + if (b) b2 = x2 + x2l; // b follows x as last content part + cd2 = b2 + bl; // CD follows B2 + } + else + { // move xmp to end + if (b) b2 = al; // b follows + // x follows as last content part (B existing or not) + x2 = al + bl; + x2l = xmpFileHeader.sizeTotalCF(); + cd2 = x2 + x2l; // CD follows X + } + + /// create new XMP header /////////////////////////////////////////////////// + // written into actual fields + generation of extraField at .write()-time... + // however has impact on .size() computation -- thus enter before cdx2l computation + xmpCDHeader.sizeUncompressed = uncomprPacketLen; + xmpCDHeader.sizeCompressed = finalPacketLen; + xmpCDHeader.offsetLocalHeader = x2; + PutUns32LE ( crc, &xmpCDHeader.fields[CDFileHeader::o_crc32] ); + PutUns16LE ( compressXMP ? 8:0, &xmpCDHeader.fields[CDFileHeader::o_compression] ); + PutUns16LE ( lastModDate, &xmpCDHeader.fields[CDFileHeader::o_lastmodDate] ); + PutUns16LE ( lastModTime, &xmpCDHeader.fields[CDFileHeader::o_lastmodTime] ); + + // for + if ( inPlacePossible ) + { + cdx2 = cdx; //same, same + writeOut( file, file, false, true ); + return; + } + + //////////////////////////////////////////////////////////////////////// + // temporarily store (those few, small) trailing things that might not survive the move around: + LFA_Seek(file, cd, SEEK_SET); // seek to central directory + cdEntries.clear(); //mac precaution + + ////////////////////////////////////////////////////////////////////////////// + // parse headers + // * stick together output header list + cd2l = 0; //sum up below + + CDFileHeader tempHeader; + for( XMP_Uns16 pos=1 ; pos <= numCF ; pos++ ) + { + if ( (cdx) && (LFA_Tell( file ) == cdx) ) + { + tempHeader.read( file ); //read, even if not use, to advance file pointer + } + else + { + tempHeader.read( file ); + // adjust b2 offset for files that were behind the xmp: + // may (if xmp moved to back) + // or may not (inPlace Update) make a difference + if ( (x) && ( tempHeader.offsetLocalHeader > x) ) // if xmp existed before and this was a file behind it + tempHeader.offsetLocalHeader += b2 - b; + cd2l += tempHeader.size(); // prior offset change might have impact + cdEntries.push_back( tempHeader ); + } + } + + //push in XMP packet as last one (new or not) + cdEntries.push_back( xmpCDHeader ); + cdx2l = xmpCDHeader.size(); + cd2l += cdx2l; // true, no matter which order + + //OLD cd2l = : cdl - cdxl + cdx2l; // (NB: cdxl might be 0) + numCF2 = numCF + ( (cdx)?0:1 ); //xmp packet for the first time? -> add one more CF + + XMP_Validate( numCF2 > 0, "no content files", kXMPErr_BadFileFormat ); + XMP_Validate( numCF2 <= 0xFFFE, "max number of 0xFFFE entries reached", kXMPErr_BadFileFormat ); + + cdx2 = cd2 + cd2l - cdx2l; // xmp content entry comes last (since beyond inPlace Update) + + // zip64 decision + if ( ( cd2 + cd2l + hl ) > 0xffffffff ) // predict non-zip size ==> do we need a zip-64? + { + z2 = cd2 + cd2l; + z2l = Zip64EndOfDirectory::FIXED_SIZE + Zip64Locator::TOTAL_SIZE; + } + + // header and output length, + h2 = cd2 + cd2l + z2l; // (z2l might be 0) + f2l = h2 + hl; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // read H (endOfCD), correct offset + LFA_Seek(file, h, SEEK_SET); + + endOfCD.read( file ); + if ( cd2 <= 0xffffffff ) + PutUns32LE( (XMP_Int32) cd2 , &endOfCD.fields[ endOfCD.o_CdOffset ] ); + else + PutUns32LE( 0xffffffff , &endOfCD.fields[ endOfCD.o_CdOffset ] ); + PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesDisk ] ); + PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesTotal ] ); + + XMP_Enforce( cd2l <= 0xffffffff ); // _size_ of directory itself certainly under 4GB + PutUns32LE( (XMP_Uns32)cd2l, &endOfCD.fields[ endOfCD.o_CdSize ] ); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // MOVING + writeOut( file, file, false, false ); + + this->needsUpdate = false; //do last for safety reasons +} // UCF_MetaHandler::UpdateFile + +// ================================================================================================= +// UCF_MetaHandler::WriteFile +// ============================ +void UCF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) +{ + IgnoreParam ( sourcePath ); + XMP_Throw ( "UCF_MetaHandler::WriteFile: TO BE IMPLEMENTED", kXMPErr_Unimplemented ); +} + +// ================================================================================================= +// own approach to unify Update and WriteFile: +// ============================ + +void UCF_MetaHandler::writeOut( LFA_FileRef sourceFile, LFA_FileRef targetFile, bool isRewrite, bool isInPlace) +{ + // isInPlace is only possible when it's not a complete rewrite + XMP_Enforce( (!isInPlace) || (!isRewrite) ); + + ///////////////////////////////////////////////////////// + // A + if (isRewrite) //move over A block + LFA_Move( sourceFile , 0 , targetFile, 0 , al ); + + ///////////////////////////////////////////////////////// + // B / X (not necessarily in this order) + if ( !isInPlace ) // B does not change a thing (important optimization) + { + LFA_Seek( targetFile , b2 , SEEK_SET ); + LFA_Move( sourceFile , b , targetFile, b2 , bl ); + } + + LFA_Seek( targetFile , x2 , SEEK_SET ); + xmpFileHeader.write( targetFile ); + LFA_Write( targetFile, finalPacketStr, finalPacketLen ); + //TODO: cover reverse case / inplace ... + + ///////////////////////////////////////////////////////// + // CD + // No Seek here on purpose. + // This assert must still be valid + + // if inPlace, the only thing that needs still correction is the CRC in CDX: + if ( isInPlace ) + { + XMP_Uns32 crc; //TEMP, not actually needed + crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] ); + + // go there, + // do the job (take value directly from (non-CD-)fileheader), + // end of story. + LFA_Seek( targetFile , cdx2 + CDFileHeader::o_crc32 , SEEK_SET ); + LFA_Write( targetFile, &xmpFileHeader.fields[FileHeader::o_crc32], 4); + + return; + } + + LFA_Seek( targetFile , cd2 , SEEK_SET ); + + std::vector<CDFileHeader>::iterator iter; + int tmptmp=1; + for( iter = cdEntries.begin(); iter != cdEntries.end(); iter++ ) { + CDFileHeader* p=&(*iter); + XMP_Int64 before = LFA_Tell(targetFile); + p->write( targetFile ); + XMP_Int64 total = LFA_Tell(targetFile) - before; + XMP_Int64 tmpSize = p->size(); + tmptmp++; + } + + ///////////////////////////////////////////////////////// + // Z + if ( z2 ) // yes, that simple + { + XMP_Assert( z2 == LFA_Tell(targetFile)); + LFA_Seek( targetFile , z2 , SEEK_SET ); + + //no use in copying, always construct from scratch + Zip64EndOfDirectory zip64EndOfDirectory( cd2, cd2l, numCF2) ; + Zip64Locator zip64Locator( z2 ); + + zip64EndOfDirectory.write( targetFile ); + zip64Locator.write( targetFile ); + } + + ///////////////////////////////////////////////////////// + // H + XMP_Assert( h2 == LFA_Tell(targetFile)); + endOfCD.write( targetFile ); + + XMP_Assert( f2l == LFA_Tell(targetFile)); + if ( f2l< fl) + LFA_Truncate(targetFile,f2l); //file may have shrunk +} + + diff --git a/source/XMPFiles/FileHandlers/UCF_Handler.hpp b/source/XMPFiles/FileHandlers/UCF_Handler.hpp new file mode 100644 index 0000000..ec60f86 --- /dev/null +++ b/source/XMPFiles/FileHandlers/UCF_Handler.hpp @@ -0,0 +1,716 @@ +#ifndef __UCF_Handler_hpp__ +#define __UCF_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 "XMPFiles_Impl.hpp" + +// ================================================================================================= +/// \file UCF_Handler.hpp +// +// underlying math: +// __ 0 ______ 0 ______ __ +// | A | | A | +// | | | | +// al | | (a2l)| | +// x |------| b2 |------| +// xl | X | | B |_ +// b |------| (b2l)| | | +// | B | x2 |------| | B2 could also be +// bl | | x2l | X2 | | _after_ X2 +// cd |------| cd2 |------|<' +// |//CD//| |//CD2/| +// cdx |------| cdx2 |------| +// cdxl|------| cdx2l|------| +// cdl|//////| cd2l|//////| +// z |------| z2|------| +// | | | | +// [zl]| Z | z2l | Z2 | +// h |------| h2 |------| +// fl | H | | H2 | f2l +// __ hl |______| (h2l)|______| __ +// +// fl file length pre (2 = post) +// numCf number of content files prior to injection +// numCf2 " post +// +// +// l length variable, all else offset +// [ ] variable is not needed +// ( ) variable is identical to left +// a content files prior to xmp (possibly: all) +// b content files behind xmp (possibly: 0) +// x xmp packet (possibly: 0) +// cd central directory +// h end of central directory +// +// z zip64 record and locator (if existing) +// +// general rules: +// the bigger A, the less rewrite effort. +// (also within the CD) +// putting XMP at the end maximizes A. +// +// bool previousXMP == x!=0 +// +// (x==0) == (cdx==0) == (xl==0) == (cdxl==0) +// +// std::vector<XMP_Uns32> cdOffsetsPre; +// +// ----------------- +// asserts: +//( 1) a == a2 == 0, making these variables obsolete +//( 2) a2l == al, this block is not touched +//( 3) b2 <= b, b is only moved closer to the beginning of file +//( 4) b2l == bl, b does not change in size +//( 5) x2 >= x, b is only moved further down in the file +// +//( 6) x != 0, x2l != 0, cd != 0, cdl != 0 +// none of these blocks is at the beginning ('mimetype' by spec), +// nor is any of them zero byte long +//( 7) h!=0, hl >= 22 header is not at the beginning, minimum size 22 +// +// file size computation: +//( 8) al + bl + xl +cdl +hl = fl +//( 9) al + bl + x2l+cd2l+hl = fl2 +// +//(10) ( x==0 ) <=> ( cdx == 0 ) +// if there's a packet in the pre-file, or there isn't +//(11) (x==0) => xl=0 +//(12) (cdx==0)=> cdx=0 +// +//(13) x==0 ==> b,bl,b2,b2l==0 +// if there is no pre-xmp, B does not exist +//(14) x!=0 ==> al:=x, b:=x+xl, bl:=cd-b +// +// zip 64: +//(15) zl and z2l are basically equal, except _one_ of them is 0 : +// +//(16) b2l is indeed never different t +// +// FIXED_SIZE means the fixed (minimal) portion of a struct +// TOTAL_SIZE indicates, that this struct indeed has a fixed, known total length +// +// ================================================================================================= + +extern XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool UCF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + LFA_FileRef fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kUCF_HandlerFlags = ( + kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + /* kXMPFiles_PrefersInPlace | removed, only reasonable for formats where difference is significant */ + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + // *** kXMPFiles_AllowsSafeUpdate | + kXMPFiles_NeedsReadOnlyPacket //UCF/zip has checksums... + ); + +enum { // data descriptor + // may or may not have a signature: 0x08074b50 + kUCF_DD_crc32 = 0, + kUCF_DD_sizeCompressed = 4, + kUCF_DD_sizeUncompressed = 8, +}; + +class UCF_MetaHandler : public XMPFileHandler +{ +public: + UCF_MetaHandler ( XMPFiles * _parent ); + ~UCF_MetaHandler(); + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); + +protected: + const static XMP_Uns16 xmpFilenameLen = 21; + const static char* xmpFilename; + +private: + class Zip64EndOfDirectory { + private: + + public: + const static XMP_Uns16 o_sig = 0; // 0x06064b50 + const static XMP_Uns16 o_size = 4; // of this, excluding leading 12 bytes + // == FIXED_SIZE -12, since we're never creating the extensible data sector... + const static XMP_Uns16 o_VersionMade = 12; + const static XMP_Uns16 o_VersionNeededExtr = 14; + const static XMP_Uns16 o_numDisk = 16; // force 0 + const static XMP_Uns16 o_numCDDisk = 20; // force 0 + const static XMP_Uns16 o_numCFsThisDisk = 24; + const static XMP_Uns16 o_numCFsTotal = 32; // force equal + const static XMP_Uns16 o_sizeOfCD = 40; // (regular one, not Z64) + const static XMP_Uns16 o_offsetCD = 48; // " + + const static XMP_Int32 FIXED_SIZE = 56; + char fields[FIXED_SIZE]; + + const static XMP_Uns32 ID = 0x06064b50; + + Zip64EndOfDirectory( XMP_Int64 offsetCD, XMP_Int64 sizeOfCD, XMP_Uns64 numCFs ) + { + memset(fields,'\0',FIXED_SIZE); + + PutUns32LE(ID ,&fields[o_sig] ); + PutUns64LE(FIXED_SIZE - 12, &fields[o_size] ); //see above + PutUns16LE( 45 ,&fields[o_VersionMade] ); + PutUns16LE( 45 ,&fields[o_VersionNeededExtr] ); + // fine at 0: o_numDisk + // fine at 0: o_numCDDisk + PutUns64LE( numCFs, &fields[o_numCFsThisDisk] ); + PutUns64LE( numCFs, &fields[o_numCFsTotal] ); + PutUns64LE( sizeOfCD, &fields[o_sizeOfCD] ); + PutUns64LE( offsetCD, &fields[o_offsetCD] ); + } + + void write(LFA_FileRef file) + { + XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat ); + LFA_Write( file , fields , FIXED_SIZE ); + } + + }; + + class Zip64Locator { + public: + const static XMP_Uns16 o_sig = 0; // 0x07064b50 + const static XMP_Uns16 o_numDiskZ64CD = 4; // force 0 + const static XMP_Uns16 o_offsZ64EOD = 8; + const static XMP_Uns16 o_numDisks = 16; // set 1, tolerate 0 + + const static XMP_Int32 TOTAL_SIZE = 20; + char fields[TOTAL_SIZE]; + + const static XMP_Uns32 ID = 0x07064b50; + + Zip64Locator( XMP_Int64 offsetZ64EOD ) + { + memset(fields,'\0',TOTAL_SIZE); + PutUns32LE(ID, &fields[Zip64Locator::o_sig] ); + PutUns32LE(0, &fields[Zip64Locator::o_numDiskZ64CD] ); + PutUns64LE(offsetZ64EOD, &fields[Zip64Locator::o_offsZ64EOD] ); + PutUns32LE(1, &fields[Zip64Locator::o_numDisks] ); + } + + // writes structure to file (starting at current position) + void write(LFA_FileRef file) + { + XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat ); + LFA_Write( file , fields , TOTAL_SIZE ); + } + }; + + struct EndOfDirectory { + public: + const static XMP_Int32 FIXED_SIZE = 22; //32 bit type is important to not overrun on maxcomment + const static XMP_Uns32 ID = 0x06054b50; + const static XMP_Int32 COMMENT_MAX = 0xFFFF; + //offsets + const static XMP_Int32 o_CentralDirectorySize = 12; + const static XMP_Int32 o_CentralDirectoryOffset = 16; + }; + + class FileHeader { + private: + //TODO intergrate in clear() + void release() // avoid terminus free() since subject to a #define (mem-leak-check) + { + if (filename) delete filename; + if (extraField) delete extraField; + filename=0; + extraField=0; + } + + public: + const static XMP_Uns32 SIG = 0x04034b50; + const static XMP_Uns16 kdataDescriptorFlag = 0x8; + + const static XMP_Uns16 o_sig = 0; + const static XMP_Uns16 o_extractVersion = 4; + const static XMP_Uns16 o_flags = 6; + const static XMP_Uns16 o_compression = 8; + const static XMP_Uns16 o_lastmodTime = 10; + const static XMP_Uns16 o_lastmodDate = 12; + const static XMP_Uns16 o_crc32 = 14; + const static XMP_Uns16 o_sizeCompressed = 18; + const static XMP_Uns16 o_sizeUncompressed = 22; + const static XMP_Uns16 o_fileNameLength = 26; + const static XMP_Uns16 o_extraFieldLength = 28; + // total 30 + + const static int FIXED_SIZE = 30; + char fields[FIXED_SIZE]; + + char* filename; + char* extraField; + XMP_Uns16 filenameLen; + XMP_Uns16 extraFieldLen; + + void clear() + { + this->release(); + memset(fields,'\0',FIXED_SIZE); + //arm with minimal default values: + PutUns32LE(0x04034b50, &fields[FileHeader::o_sig] ); + PutUns16LE(0x14, &fields[FileHeader::o_extractVersion] ); + } + + FileHeader() : filename(0),filenameLen(0),extraField(0),extraFieldLen(0) + { + clear(); + }; + + // reads entire *FileHeader* structure from file (starting at current position) + void read(LFA_FileRef file) + { + this->release(); + + LFA_Read( file , fields , FIXED_SIZE , true ); + + XMP_Uns32 tmp32 = GetUns32LE( &this->fields[FileHeader::o_sig] ); + XMP_Validate( SIG == tmp32, "invalid header", kXMPErr_BadFileFormat ); + filenameLen = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] ); + extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] ); + + // nb unlike the CDFileHeader the FileHeader will in practice never have + // extra fields. Reasoning: File headers never carry (their own) offsets, + // (un)compressed size of XMP will hardly ever reach 4 GB + + if (filenameLen) { + filename = new char[filenameLen]; + LFA_Read(file,filename,filenameLen,true); + } + if (extraFieldLen) { + extraField = new char[extraFieldLen]; + LFA_Read(file,extraField,extraFieldLen,true); + // *** NB: this WOULD need parsing for content files that are + // compressed or uncompressed >4GB (VERY unlikely for XMP) + } + } + + // writes structure to file (starting at current position) + void write(LFA_FileRef file) + { + XMP_Validate( SIG == GetUns32LE( &this->fields[FileHeader::o_sig] ), "invalid header on write", kXMPErr_BadFileFormat ); + + filenameLen = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] ); + extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] ); + + LFA_Write( file , fields , FIXED_SIZE ); + if (filenameLen) LFA_Write( file, filename, filenameLen ); + if (extraFieldLen) LFA_Write( file, extraField,extraFieldLen ); + } + + void transfer(const FileHeader &orig) + { + memcpy(fields,orig.fields,FIXED_SIZE); + if (orig.extraField) + { + extraFieldLen=orig.extraFieldLen; + extraField = new char[extraFieldLen]; + memcpy(extraField,orig.extraField,extraFieldLen); + } + if (orig.filename) + { + filenameLen=orig.filenameLen; + filename = new char[filenameLen]; + memcpy(filename,orig.filename,filenameLen); + } + }; + + void setXMPFilename() + { + // only needed for fresh structs, thus enforcing rather than catering to memory issues + XMP_Enforce( (filenameLen==0) && (extraFieldLen == 0) ); + filenameLen = xmpFilenameLen; + PutUns16LE(filenameLen, &fields[FileHeader::o_fileNameLength] ); + filename = new char[xmpFilenameLen]; + memcpy(filename,"META-INF/metadata.xml",xmpFilenameLen); + } + + XMP_Uns32 sizeHeader() + { + return this->FIXED_SIZE + this->filenameLen + this->extraFieldLen; + } + + XMP_Uns32 sizeTotalCF() + { + //*** not zip64 bit safe yet, use only for non-large xmp packet + return this->sizeHeader() + GetUns32LE( &fields[FileHeader::o_sizeCompressed] ); + } + + ~FileHeader() + { + this->release(); + }; + + }; //class FileHeader + + ////// yes, this needs an own class + ////// offsets must be extracted, added, modified, + ////// come&go depending on being >0xffffff + ////class extraField { + //// private: + + + class CDFileHeader { + private: + void release() //*** needed or can go? + { + if (filename) delete filename; + if (extraField) delete extraField; + if (comment) delete comment; + filename=0; filenameLen=0; + extraField=0; extraFieldLen=0; + comment=0; commentLen=0; + } + + const static XMP_Uns32 SIG = 0x02014b50; + + public: + const static XMP_Uns16 o_sig = 0; //0x02014b50 + const static XMP_Uns16 o_versionMadeBy = 4; + const static XMP_Uns16 o_extractVersion = 6; + const static XMP_Uns16 o_flags = 8; + const static XMP_Uns16 o_compression = 10; + const static XMP_Uns16 o_lastmodTime = 12; + const static XMP_Uns16 o_lastmodDate = 14; + const static XMP_Uns16 o_crc32 = 16; + const static XMP_Uns16 o_sizeCompressed = 20; // 16bit stub + const static XMP_Uns16 o_sizeUncompressed = 24; // 16bit stub + const static XMP_Uns16 o_fileNameLength = 28; + const static XMP_Uns16 o_extraFieldLength = 30; + const static XMP_Uns16 o_commentLength = 32; + const static XMP_Uns16 o_diskNo = 34; + const static XMP_Uns16 o_internalAttribs = 36; + const static XMP_Uns16 o_externalAttribs = 38; + const static XMP_Uns16 o_offsetLocalHeader = 42; // 16bit stub + // total size is 4+12+12+10+8=46 + + const static int FIXED_SIZE = 46; + char fields[FIXED_SIZE]; + + // do not bet on any zero-freeness, + // certainly no zero termination (pascal strings), + // treat as data blocks + char* filename; + char* extraField; + char* comment; + XMP_Uns16 filenameLen; + XMP_Uns16 extraFieldLen; + XMP_Uns16 commentLen; + + // full, real, parsed 64 bit values + XMP_Int64 sizeUncompressed; + XMP_Int64 sizeCompressed; + XMP_Int64 offsetLocalHeader; + + CDFileHeader() : filename(0),extraField(0),comment(0),filenameLen(0), + extraFieldLen(0),commentLen(0),sizeUncompressed(0),sizeCompressed(0),offsetLocalHeader(0) + { + memset(fields,'\0',FIXED_SIZE); + //already arm with appropriate values where applicable: + PutUns32LE(0x02014b50, &fields[CDFileHeader::o_sig] ); + PutUns16LE(0x14, &fields[CDFileHeader::o_extractVersion] ); + }; + + // copy constructor + CDFileHeader(const CDFileHeader& orig) : filename(0),extraField(0),comment(0),filenameLen(0), + extraFieldLen(0),commentLen(0),sizeUncompressed(0),sizeCompressed(0),offsetLocalHeader(0) + { + memcpy(fields,orig.fields,FIXED_SIZE); + if (orig.extraField) + { + extraFieldLen=orig.extraFieldLen; + extraField = new char[extraFieldLen]; + memcpy(extraField , orig.extraField , extraFieldLen); + } + if (orig.filename) + { + filenameLen=orig.filenameLen; + filename = new char[filenameLen]; + memcpy(filename , orig.filename , filenameLen); + } + if (orig.comment) + { + commentLen=orig.commentLen; + comment = new char[commentLen]; + memcpy(comment , orig.comment , commentLen); + } + + filenameLen = orig.filenameLen; + extraFieldLen = orig.extraFieldLen; + commentLen = orig.commentLen; + + sizeUncompressed = orig.sizeUncompressed; + sizeCompressed = orig.sizeCompressed; + offsetLocalHeader = orig.offsetLocalHeader; + } + + // Assignment operator + CDFileHeader& operator=(const CDFileHeader& obj) + { + XMP_Throw("not supported",kXMPErr_Unimplemented); + } + + // reads entire structure from file (starting at current position) + void read(LFA_FileRef file) + { + this->release(); + + LFA_Read(file,fields,FIXED_SIZE,true); + XMP_Validate( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ), "invalid header", kXMPErr_BadFileFormat ); + + filenameLen = GetUns16LE( &this->fields[CDFileHeader::o_fileNameLength] ); + extraFieldLen = GetUns16LE( &this->fields[CDFileHeader::o_extraFieldLength] ); + commentLen = GetUns16LE( &this->fields[CDFileHeader::o_commentLength] ); + + if (filenameLen) { + filename = new char[filenameLen]; + LFA_Read(file,filename,filenameLen,true); + } + if (extraFieldLen) { + extraField = new char[extraFieldLen]; + LFA_Read(file,extraField,extraFieldLen,true); + } + if (commentLen) { + comment = new char[commentLen]; + LFA_Read(file,comment,commentLen,true); + } + + ////// GET ACTUAL 64 BIT VALUES ////////////////////////////////////////////// + // get 32bit goodies first, correct later + sizeUncompressed = GetUns32LE( &fields[o_sizeUncompressed] ); + sizeCompressed = GetUns32LE( &fields[o_sizeCompressed] ); + offsetLocalHeader = GetUns32LE( &fields[o_offsetLocalHeader] ); + + XMP_Int32 offset = 0; + while ( offset < extraFieldLen ) + { + XMP_Validate( (extraFieldLen - offset) >= 4, "need 4 bytes for next header ID+len", kXMPErr_BadFileFormat); + XMP_Uns16 headerID = GetUns16LE( &extraField[offset] ); + XMP_Uns16 dataSize = GetUns16LE( &extraField[offset+2] ); + offset += 4; + + XMP_Validate( (extraFieldLen - offset) <= dataSize, + "actual field lenght not given", kXMPErr_BadFileFormat); + if ( headerID == 0x1 ) //we only care about "Zip64 extended information extra field" + { + XMP_Validate( offset < extraFieldLen, "extra field too short", kXMPErr_BadFileFormat); + if (sizeUncompressed == 0xffffffff) + { + sizeUncompressed = GetUns64LE( &extraField[offset] ); + offset += 8; + } + if (sizeCompressed == 0xffffffff) + { + sizeCompressed = GetUns64LE( &extraField[offset] ); + offset += 8; + } + if (offsetLocalHeader == 0xffffffff) + { + offsetLocalHeader = GetUns64LE( &extraField[offset] ); + offset += 8; + } + } + else + { + offset += dataSize; + } // if + } // while + } // read() + + // writes structure to file (starting at current position) + void write(LFA_FileRef file) + { + //// WRITE BACK REAL 64 BIT VALUES, CREATE EXTRA FIELD /////////////// + //may only wipe extra field after obtaining all Info from it + if (extraField) delete extraField; + extraFieldLen=0; + + if ( ( sizeUncompressed > 0xffffffff ) || + ( sizeCompressed > 0xffffffff ) || + ( offsetLocalHeader > 0xffffffff ) ) + { + extraField = new char[64]; // actual maxlen is 32 + extraFieldLen = 4; //first fields are for ID, size + if ( sizeUncompressed > 0xffffffff ) + { + PutUns64LE( sizeUncompressed, &extraField[extraFieldLen] ); + extraFieldLen += 8; + sizeUncompressed = 0xffffffff; + } + if ( sizeCompressed > 0xffffffff ) + { + PutUns64LE( sizeCompressed, &extraField[extraFieldLen] ); + extraFieldLen += 8; + sizeCompressed = 0xffffffff; + } + if ( offsetLocalHeader > 0xffffffff ) + { + PutUns64LE( offsetLocalHeader, &extraField[extraFieldLen] ); + extraFieldLen += 8; + offsetLocalHeader = 0xffffffff; + } + + //write ID, dataSize + PutUns16LE( 0x0001, &extraField[0] ); + PutUns16LE( extraFieldLen-4, &extraField[2] ); + //extraFieldSize + PutUns16LE( extraFieldLen, &this->fields[CDFileHeader::o_extraFieldLength] ); + } + + // write out 32-bit ('ff-stubs' or not) + PutUns32LE( (XMP_Uns32)sizeUncompressed, &fields[o_sizeUncompressed] ); + PutUns32LE( (XMP_Uns32)sizeCompressed, &fields[o_sizeCompressed] ); + PutUns32LE( (XMP_Uns32)offsetLocalHeader, &fields[o_offsetLocalHeader] ); + + /// WRITE ///////////////////////////////////////////////////////////////// + XMP_Enforce( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ) ); + + LFA_Write( file , fields , FIXED_SIZE ); + if (filenameLen) LFA_Write( file, filename , filenameLen ); + if (extraFieldLen) LFA_Write( file, extraField , extraFieldLen ); + if (commentLen) LFA_Write( file, extraField , extraFieldLen ); + } + + void setXMPFilename() + { + if (filename) delete filename; + filenameLen = xmpFilenameLen; + filename = new char[xmpFilenameLen]; + PutUns16LE(filenameLen, &fields[CDFileHeader::o_fileNameLength] ); + memcpy(filename,"META-INF/metadata.xml",xmpFilenameLen); + } + + XMP_Int64 size() + { + XMP_Int64 r = this->FIXED_SIZE + this->filenameLen + this->commentLen; + // predict serialization size + if ( (sizeUncompressed > 0xffffffff)||(sizeCompressed > 0xffffffff)||(offsetLocalHeader>0xffffffff) ) + { + r += 4; //extra fields necessary + if (sizeUncompressed > 0xffffffff) r += 8; + if (sizeCompressed > 0xffffffff) r += 8; + if (offsetLocalHeader > 0xffffffff) r += 8; + } + return r; + } + + ~CDFileHeader() + { + this->release(); + }; + }; // class CDFileHeader + + class EndOfCD { + private: + const static XMP_Uns32 SIG = 0x06054b50; + void UCFECD_Free() + { + if(commentLen) delete comment; + commentLen = 0; + } + public: + const static XMP_Int32 o_Sig = 0; + const static XMP_Int32 o_CdNumEntriesDisk = 8; // same-same for UCF, since single-volume + const static XMP_Int32 o_CdNumEntriesTotal = 10;// must update both + const static XMP_Int32 o_CdSize = 12; + const static XMP_Int32 o_CdOffset = 16; + const static XMP_Int32 o_CommentLen = 20; + + const static int FIXED_SIZE = 22; + char fields[FIXED_SIZE]; + + char* comment; + XMP_Uns16 commentLen; + + EndOfCD() : comment(0), commentLen(0) + { + //nothing + }; + + void read (LFA_FileRef file) + { + UCFECD_Free(); + + LFA_Read(file,fields,FIXED_SIZE,true); + XMP_Validate( this->SIG == GetUns32LE( &this->fields[o_Sig] ), "invalid header", kXMPErr_BadFileFormat ); + + commentLen = GetUns16LE( &this->fields[o_CommentLen] ); + if(commentLen) + { + comment = new char[commentLen]; + LFA_Read(file,comment,commentLen,true); + } + }; + + void write(LFA_FileRef file) + { + XMP_Enforce( this->SIG == GetUns32LE( &this->fields[o_Sig] ) ); + commentLen = GetUns16LE( &this->fields[o_CommentLen] ); + LFA_Write( file , fields , FIXED_SIZE ); + if (commentLen) + LFA_Write ( file, comment, commentLen ); + } + + ~EndOfCD() + { + if (comment) delete comment; + }; + }; //class EndOfCD + + //////////////////////////////////////////////////////////////////////////////////// + // EMBEDDING MATH + // + // a = content files before xmp (always 0 thus ommited) + // b/b2 = content files behind xmp (before/after injection) + // x/x2 = offset xmp content header + content file (before/after injection) + // cd/cd = central directory + // h/h2 = end of central directory record + XMP_Int64 b,b2,x,x2,cd,cd2,cdx,cdx2,z,z2,h,h2, + // length thereof ('2' only where possibly different) + // using XMP_Int64 here also for length (not XMP_Int32), + // to be prepared for zip64, our LFA functions might need things in multiple chunks... + al,bl,xl,x2l,cdl,cd2l,cdxl,cdx2l,z2l,hl,fl,f2l; + XMP_Uns16 numCF,numCF2; + + bool wasCompressed; // ..before, false if no prior xmp + bool compressXMP; // compress this time? + bool inPlacePossible; + /* bool isZip64; <=> z2 != 0 */ + + FileHeader xmpFileHeader; + CDFileHeader xmpCDHeader; + + XMP_StringPtr uncomprPacketStr; + XMP_StringLen uncomprPacketLen; + XMP_StringPtr finalPacketStr; + XMP_StringLen finalPacketLen; + std::vector<CDFileHeader> cdEntries; + EndOfCD endOfCD; + void writeOut( LFA_FileRef sourceFile, LFA_FileRef targetFile, bool isRewrite, bool isInPlace); + +}; // UCF_MetaHandler + +// ================================================================================================= + +#endif /* __UCF_Handler_hpp__ */ + + diff --git a/source/XMPFiles/FileHandlers/WAV_Handler.cpp b/source/XMPFiles/FileHandlers/WAV_Handler.cpp index 1cd5eb7..6820d01 100644 --- a/source/XMPFiles/FileHandlers/WAV_Handler.cpp +++ b/source/XMPFiles/FileHandlers/WAV_Handler.cpp @@ -1,13 +1,16 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 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. // ================================================================================================= -#if WIN_ENV +#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 @@ -103,7 +106,7 @@ using namespace std; static inline int GetStringRiffSize ( const std::string & str ) { - int l = strlen ( const_cast<char *> (str.data()) ); + int l = (int)strlen ( const_cast<char *> (str.data()) ); if ( l & 1 ) ++l; return l; } @@ -194,7 +197,7 @@ void WAV_MetaHandler::UpdateFile ( bool doSafeUpdate ) 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 @@ -227,7 +230,7 @@ void WAV_MetaHandler::UpdateFile ( bool doSafeUpdate ) 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 ); @@ -239,10 +242,11 @@ void WAV_MetaHandler::UpdateFile ( bool doSafeUpdate ) digestStr += hexDigits [byte & 0xF]; } - XMP_StringLen oldLen = this->xmpPacket.size(); + 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_ExactPacketLength, oldLen ); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength), + oldLen ); } catch ( ... ) { this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); } @@ -250,29 +254,33 @@ void WAV_MetaHandler::UpdateFile ( bool doSafeUpdate ) } XMP_StringPtr packetStr = this->xmpPacket.c_str(); - XMP_StringLen packetLen = this->xmpPacket.size(); + 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 (' '); + 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 = this->xmpPacket.size(); + 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); + 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(), strTitle.size() ); + PutChunk ( fileRef, riffState, wavWaveTag, wavWaveTitleChunk, strTitle.c_str(), (XMP_Int32)strTitle.size() ); // Pad the old tags RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, 0, wavInfoCreateDateChunk, 0 ); @@ -360,14 +368,14 @@ static void AddDigestItem ( XMP_Uns32 legacyID, std::string & legacyStr, std::st { XMP_Uns32 leID = MakeUns32LE ( legacyID ); - XMP_Uns32 leLen = MakeUns32LE (legacyStr.size()); + 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(), legacyStr.size() ); + MD5Update ( md5, (XMP_Uns8*)legacyStr.c_str(), (XMP_Int32)legacyStr.size() ); } // AddDigestItem @@ -409,7 +417,7 @@ static void CreateCurrentDigest ( LFA_FileRef fileRef, RIFF_Support::RiffState r 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] = ';'; @@ -434,15 +442,47 @@ void WAV_MetaHandler::CacheFileData() bool keepExistingXMP = false; // By default an import will replace existing XMP. bool haveLegacyItem, haveXMPItem; - LFA_FileRef fileRef ( this->parent->fileRef ); + 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; + 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, 0, 0, 0, &bufferSize ); + haveLegacyItem = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType /* _PMX, the xmp packet */, 0, 0, 0, &bufferSize ); if ( ! haveLegacyItem ) { @@ -455,12 +495,13 @@ void WAV_MetaHandler::CacheFileData() this->xmpPacket.assign(bufferSize, ' '); // Get the metadata - haveLegacyItem = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType, 0, 0, - const_cast<char *>(this->xmpPacket.data()), &bufferSize ); + 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 = kXMPFiles_UnknownOffset; + this->packetInfo.offset = xmpPacketPosition; this->packetInfo.length = bufferSize; - this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), this->xmpPacket.size() ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); this->containsXMP = true; } @@ -502,10 +543,24 @@ void WAV_MetaHandler::CacheFileData() } + CreatorAtom::Import ( this->xmpObj, fileRef, riffState ); + // Update the xmpPacket, as the xmpObj might have been updated with legacy info - this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); - this->packetInfo.offset = kXMPFiles_UnknownOffset; - this->packetInfo.length = this->xmpPacket.size(); + 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; @@ -646,3 +701,7 @@ void WAV_MetaHandler::ImportLegacyItem ( RIFF_Support::RiffState & inOutRiffStat } } // WAV_MetaHandler::LoadPropertyFromRIFF + +// ================================================================================================= + +#endif // XMP_UNIXBuild diff --git a/source/XMPFiles/FileHandlers/WAV_Handler.hpp b/source/XMPFiles/FileHandlers/WAV_Handler.hpp index a84f46b..4bd9aae 100644 --- a/source/XMPFiles/FileHandlers/WAV_Handler.hpp +++ b/source/XMPFiles/FileHandlers/WAV_Handler.hpp @@ -3,13 +3,16 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED -// Copyright 2002-2007 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" @@ -68,4 +71,5 @@ private: // ================================================================================================= -#endif /* __WAV_Handler_hpp__ */ +#endif // XMP_UNIXBuild +#endif // __WAV_Handler_hpp__ diff --git a/source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp new file mode 100644 index 0000000..47c0851 --- /dev/null +++ b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp @@ -0,0 +1,824 @@ +// ================================================================================================= +// 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 "XDCAMEX_Handler.hpp" +#include "XDCAM_Support.hpp" +#include "MD5.h" + +using namespace std; + +// ================================================================================================= +/// \file XDCAMEX_Handler.cpp +/// \brief Folder format handler for XDCAMEX. +/// +/// This handler is for the XDCAMEX video format. +/// +/// .../MyMovie/ +/// BPAV/ +/// MEDIAPRO.XML +/// MEDIAPRO.BUP +/// CLPR/ +/// 709_001_01/ +/// 709_001_01.SMI +/// 709_001_01.MP4 +/// 709_001_01M01.XML +/// 709_001_01R01.BIM +/// 709_001_01I01.PPN +/// 709_001_02/ +/// 709_002_01/ +/// 709_003_01/ +/// TAKR/ +/// 709_001/ +/// 709_001.SMI +/// 709_001M01.XML +/// +/// The Backup files (.BUP) are optional. No files or directories other than those listed are +/// allowed in the BPAV directory. The CLPR (clip root) directory may contain only clip directories, +/// which may only contain the clip files listed. The TAKR (take root) direcory may contail only +/// take directories, which may only contain take files. The take root directory can be empty. +/// MEDIPRO.XML contains information on clip and take management. +/// +/// Each clip directory contains a media file (.MP4), a clip info file (.SMI), a real time metadata +/// file (.BIM), a non real time metadata file (.XML), and a picture pointer file (.PPN). A take +/// directory conatins a take info and non real time take metadata files. +// ================================================================================================= + +// ================================================================================================= +// XDCAMEX_CheckFormat +// =================== +// +// This version checks for the presence of a top level BPAV directory, and the required files and +// directories immediately within it. The CLPR and TAKR subfolders are required, as is MEDIAPRO.XML. +// +// The state of the string parameters depends on the form of the path passed by the client. If the +// client passed a logical clip path, like ".../MyMovie/012_3456_01", the parameters are: +// rootPath - ".../MyMovie" +// gpName - empty +// parentName - empty +// leafName - "012_3456_01" +// If the client passed a full file path, like ".../MyMovie/BPAV/CLPR/012_3456_01/012_3456_01M01.XML", they are: +// rootPath - ".../MyMovie/BPAV" +// gpName - "CLPR" +// parentName - "012_3456_01" +// leafName - "012_3456_01M01" + +// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +// ! Using explicit '/' as a separator when creating paths, it works on Windows. + +bool XDCAMEX_CheckFormat ( XMP_FileFormat format, + const std::string & _rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & _leafName, + XMPFiles * parent ) +{ + std::string rootPath = _rootPath; + std::string clipName = _leafName; + std::string grandGPName; + + std::string bpavPath ( rootPath ); + + // Do some initial checks on the gpName and parentName. + + if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty. + + if ( gpName.empty() ) { + + // This is the logical clip path case. Make sure .../MyMovie/BPAV/CLPR is a folder. + bpavPath += kDirChar; // The rootPath was just ".../MyMovie". + bpavPath += "BPAV"; + if ( GetChildMode ( bpavPath, "CLPR" ) != kFMode_IsFolder ) return false; + + } else { + + // This is the explicit file case. Make sure the ancestry is OK. Set the clip name from the + // parent folder name. + + if ( gpName != "CLPR" ) return false; + SplitLeafName ( &rootPath, &grandGPName ); + MakeUpperCase ( &grandGPName ); + if ( grandGPName != "BPAV" ) return false; + if ( ! XMP_LitNMatch ( parentName.c_str(), clipName.c_str(), parentName.size() ) ) return false; + + clipName = parentName; + + } + + // Check the rest of the required general structure. + if ( GetChildMode ( bpavPath, "TAKR" ) != kFMode_IsFolder ) return false; + if ( GetChildMode ( bpavPath, "MEDIAPRO.XML" ) != kFMode_IsFile ) return false; + + // Make sure the clip's .MP4 and .SMI files exist. + std::string tempPath ( bpavPath ); + tempPath += kDirChar; + tempPath += "CLPR"; + tempPath += kDirChar; + tempPath += clipName; + tempPath += kDirChar; + tempPath += clipName; + tempPath += ".MP4"; + if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false; + tempPath.erase ( tempPath.size()-3 ); + tempPath += "SMI"; + if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false; + + // And now save the psuedo path for the handler object. + tempPath = rootPath; + tempPath += kDirChar; + tempPath += clipName; + size_t pathLen = tempPath.size() + 1; // Include a terminating nul. + parent->handlerTemp = malloc ( pathLen ); + if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory ); + memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen ); + + return true; + +} // XDCAMEX_CheckFormat + +// ================================================================================================= +// XDCAMEX_MetaHandlerCTor +// ======================= + +XMPFileHandler * XDCAMEX_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new XDCAMEX_MetaHandler ( parent ); + +} // XDCAMEX_MetaHandlerCTor + +// ================================================================================================= +// XDCAMEX_MetaHandler::XDCAMEX_MetaHandler +// ======================================== + +XDCAMEX_MetaHandler::XDCAMEX_MetaHandler ( XMPFiles * _parent ) : expat(0) +{ + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kXDCAMEX_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + // Extract the root path and clip name from handlerTemp. + + XMP_Assert ( this->parent->handlerTemp != 0 ); + + this->rootPath.assign ( (char*) this->parent->handlerTemp ); + free ( this->parent->handlerTemp ); + this->parent->handlerTemp = 0; + + SplitLeafName ( &this->rootPath, &this->clipName ); + +} // XDCAMEX_MetaHandler::XDCAMEX_MetaHandler + +// ================================================================================================= +// XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler +// ========================================= + +XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler() +{ + + this->CleanupLegacyXML(); + if ( this->parent->handlerTemp != 0 ) { + free ( this->parent->handlerTemp ); + this->parent->handlerTemp = 0; + } + +} // XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler + +// ================================================================================================= +// XDCAMEX_MetaHandler::MakeClipFilePath +// ===================================== + +void XDCAMEX_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix ) +{ + + *path = this->rootPath; + *path += kDirChar; + *path += "BPAV"; + *path += kDirChar; + *path += "CLPR"; + *path += kDirChar; + *path += this->clipName; + *path += kDirChar; + *path += this->clipName; + *path += suffix; + +} // XDCAMEX_MetaHandler::MakeClipFilePath + +// ================================================================================================= +// XDCAMEX_MetaHandler::MakeLegacyDigest +// ===================================== + +// *** Early hack version. + +#define kHexDigits "0123456789ABCDEF" + +void XDCAMEX_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + digestStr->erase(); + if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML. + XMP_Assert ( this->expat != 0 ); + + XMP_StringPtr xdcNS = this->xdcNS.c_str(); + XML_NodePtr legacyContext, legacyProp; + + legacyContext = this->clipMetadata->GetNamedElement ( xdcNS, "Access" ); + if ( legacyContext == 0 ) return; + + MD5_CTX context; + unsigned char digestBin [16]; + MD5Init ( &context ); + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "Creator" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "CreationDate" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "LastUpdateDate" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + MD5Final ( digestBin, &context ); + + char buffer [40]; + for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digestBin[in]; + buffer[out] = kHexDigits [ byte >> 4 ]; + buffer[out+1] = kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + digestStr->append ( buffer ); + +} // XDCAMEX_MetaHandler::MakeLegacyDigest + +// ================================================================================================= +// XDCAMEX_MetaHandler::CleanupLegacyXML +// ===================================== + +void XDCAMEX_MetaHandler::CleanupLegacyXML() +{ + + if ( ! this->defaultNS.empty() ) { + SXMPMeta::DeleteNamespace ( this->defaultNS.c_str() ); + this->defaultNS.erase(); + } + + if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; } + + clipMetadata = 0; // ! Was a pointer into the expat tree. + +} // XDCAMEX_MetaHandler::CleanupLegacyXML + +// ================================================================================================= +// XDCAMEX_MetaHandler::CacheFileData +// ================================== + +void XDCAMEX_MetaHandler::CacheFileData() +{ + XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + + // See if the clip's .XMP file exists. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); + if ( GetFileMode ( xmpPath.c_str() ) != kFMode_IsFile ) return; // No XMP. + + // Read the entire .XMP file. + + char openMode = 'r'; + if ( this->parent->openFlags & kXMPFiles_OpenForUpdate ) openMode = 'w'; + + LFA_FileRef xmpFile = LFA_Open ( xmpPath.c_str(), openMode ); + if ( xmpFile == 0 ) return; // The open failed. + + XMP_Int64 xmpLen = LFA_Measure ( xmpFile ); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "XDCAMEX XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.reserve ( (size_t)xmpLen ); + this->xmpPacket.append ( (size_t)xmpLen, ' ' ); + + XMP_Int32 ioCount = LFA_Read ( xmpFile, (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen, kLFA_RequireAll ); + XMP_Assert ( ioCount == xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + XMP_Assert ( this->parent->fileRef == 0 ); + if ( openMode == 'r' ) { + LFA_Close ( xmpFile ); + } else { + this->parent->fileRef = xmpFile; + } + + this->containsXMP = true; + +} // XDCAMEX_MetaHandler::CacheFileData + +// ================================================================================================= +// XDCAMEX_MetaHandler::GetTakeDuration +// ==================================== + +void XDCAMEX_MetaHandler::GetTakeDuration ( const std::string & takeURI, std::string & duration ) +{ + + // Some versions of gcc can't tolerate goto's across declarations. + // *** Better yet, avoid this cruft with self-cleaning objects. + #define CleanupAndExit \ + { \ + if (expat != 0) delete expat; \ + if (takeXMLFile.fileRef != 0) LFA_Close ( takeXMLFile.fileRef ); \ + return; \ + } + + duration.clear(); + + // Build a directory string to the take .xml file. + + std::string takeDir ( takeURI ); + takeDir.erase ( 0, 1 ); // Change the leading "//" to "/", then all '/' to kDirChar. + if ( kDirChar != '/' ) { + for ( size_t i = 0, limit = takeDir.size(); i < limit; ++i ) { + if ( takeDir[i] == '/' ) takeDir[i] = kDirChar; + } + } + + std::string takePath ( this->rootPath ); + takePath += kDirChar; + takePath += "BPAV"; + takePath += takeDir; + + // Replace .SMI with M01.XML. + if ( takePath.size() > 4 ) { + takePath.erase ( takePath.size() - 4, 4 ); + takePath += "M01.XML"; + } + + // Parse MEDIAPRO.XML + + XML_NodePtr takeRootElem = 0; + XML_NodePtr context = 0; + AutoFile takeXMLFile; + + takeXMLFile.fileRef = LFA_Open ( takePath.c_str(), 'r' ); + if ( takeXMLFile.fileRef == 0 ) return; // The open failed. + + ExpatAdapter * expat = XMP_NewExpatAdapter(); + if ( this->expat == 0 ) return; + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = LFA_Read ( takeXMLFile.fileRef, buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + expat->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + + expat->ParseBuffer ( 0, 0, true ); // End the parse. + LFA_Close ( takeXMLFile.fileRef ); + takeXMLFile.fileRef = 0; + + // Get the root node of the XML tree. + + XML_Node & mediaproXMLTree = expat->tree; + for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) { + if ( mediaproXMLTree.content[i]->kind == kElemNode ) { + takeRootElem = mediaproXMLTree.content[i]; + } + } + if ( takeRootElem == 0 ) CleanupAndExit + + XMP_StringPtr rlName = takeRootElem->name.c_str() + takeRootElem->nsPrefixLen; + if ( ! XMP_LitMatch ( rlName, "NonRealTimeMeta" ) ) CleanupAndExit + + // MediaProfile, Contents + XMP_StringPtr ns = takeRootElem->ns.c_str(); + context = takeRootElem->GetNamedElement ( ns, "Duration" ); + if ( context != 0 ) { + XMP_StringPtr durationValue = context->GetAttrValue ( "value" ); + if ( durationValue != 0 ) duration = durationValue; + } + + CleanupAndExit + #undef CleanupAndExit + +} + +// ================================================================================================= +// XDCAMEX_MetaHandler::GetTakeUMID +// ================================ + +void XDCAMEX_MetaHandler::GetTakeUMID ( const std::string& clipUMID, + std::string& takeUMID, + std::string& takeXMLURI ) +{ + + // Some versions of gcc can't tolerate goto's across declarations. + // *** Better yet, avoid this cruft with self-cleaning objects. + #define CleanupAndExit \ + { \ + if (expat != 0) delete expat; \ + if (mediaproXMLFile.fileRef != 0) LFA_Close ( mediaproXMLFile.fileRef ); \ + return; \ + } + + takeUMID.clear(); + takeXMLURI.clear(); + + // Build a directory string to the MEDIAPRO file. + + std::string mediapropath ( this->rootPath ); + mediapropath += kDirChar; + mediapropath += "BPAV"; + mediapropath += kDirChar; + mediapropath += "MEDIAPRO.XML"; + + // Parse MEDIAPRO.XML. + + XML_NodePtr mediaproRootElem = 0; + XML_NodePtr contentContext = 0, materialContext = 0; + + AutoFile mediaproXMLFile; + mediaproXMLFile.fileRef = LFA_Open ( mediapropath.c_str(), 'r' ); + if ( mediaproXMLFile.fileRef == 0 ) return; // The open failed. + + ExpatAdapter * expat = XMP_NewExpatAdapter(); + if ( this->expat == 0 ) return; + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = LFA_Read ( mediaproXMLFile.fileRef, buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + expat->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + + expat->ParseBuffer ( 0, 0, true ); // End the parse. + LFA_Close ( mediaproXMLFile.fileRef ); + mediaproXMLFile.fileRef = 0; + + // Get the root node of the XML tree. + + XML_Node & mediaproXMLTree = expat->tree; + for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) { + if ( mediaproXMLTree.content[i]->kind == kElemNode ) { + mediaproRootElem = mediaproXMLTree.content[i]; + } + } + + if ( mediaproRootElem == 0 ) CleanupAndExit + XMP_StringPtr rlName = mediaproRootElem->name.c_str() + mediaproRootElem->nsPrefixLen; + if ( ! XMP_LitMatch ( rlName, "MediaProfile" ) ) CleanupAndExit + + // MediaProfile, Contents + + XMP_StringPtr ns = mediaproRootElem->ns.c_str(); + contentContext = mediaproRootElem->GetNamedElement ( ns, "Contents" ); + + if ( contentContext != 0 ) { + + size_t numMaterialElems = contentContext->CountNamedElements ( ns, "Material" ); + + for ( size_t i = 0; i < numMaterialElems; ++i ) { // Iterate over Material tags. + + XML_NodePtr materialElement = contentContext->GetNamedElement ( ns, "Material", i ); + XMP_Assert ( materialElement != 0 ); + + XMP_StringPtr umid = materialElement->GetAttrValue ( "umid" ); + XMP_StringPtr uri = materialElement->GetAttrValue ( "uri" ); + + if ( umid == 0 ) umid = ""; + if ( uri == 0 ) uri = ""; + + size_t numComponents = materialElement->CountNamedElements ( ns, "Component" ); + + for ( size_t j = 0; j < numComponents; ++j ) { + + XML_NodePtr componentElement = materialElement->GetNamedElement ( ns, "Component", j ); + XMP_Assert ( componentElement != 0 ); + + XMP_StringPtr compUMID = componentElement->GetAttrValue ( "umid" ); + + if ( (compUMID != 0) && (compUMID == clipUMID) ) { + takeUMID = umid; + takeXMLURI = uri; + break; + } + + } + + if ( ! takeUMID.empty() ) break; + + } + + } + + CleanupAndExit + #undef CleanupAndExit + +} + +// ================================================================================================= +// XDCAMEX_MetaHandler::ProcessXMP +// =============================== + +void XDCAMEX_MetaHandler::ProcessXMP() +{ + + // Some versions of gcc can't tolerate goto's across declarations. + // *** Better yet, avoid this cruft with self-cleaning objects. + #define CleanupAndExit \ + { \ + bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \ + if ( ! openForUpdate ) this->CleanupLegacyXML(); \ + return; \ + } + + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + // NonRealTimeMeta -> XMP by schema. + std::string thisUMID, takeUMID, takeXMLURI, takeDuration; + std::string xmlPath; + this->MakeClipFilePath ( &xmlPath, "M01.XML" ); + + // *** 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(); + if ( this->expat == 0 ) XMP_Throw ( "XDCAMEX_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory ); + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = LFA_Read ( xmlFile.fileRef, buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + this->expat->ParseBuffer ( 0, 0, true ); // End the parse. + + 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; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) rootElem = xmlTree.content[i]; + } + + if ( rootElem == 0 ) CleanupAndExit + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + if ( ! XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) CleanupAndExit + + this->legacyNS = rootElem->ns; + + // Check the legacy digest. + + XMP_StringPtr legacyNS = this->legacyNS.c_str(); + this->clipMetadata = rootElem; // ! Save the NonRealTimeMeta pointer for other use. + + std::string oldDigest, newDigest; + bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAMEX", &oldDigest, 0 ); + if ( digestFound ) { + this->MakeLegacyDigest ( &newDigest ); + if ( oldDigest == newDigest ) CleanupAndExit + } + + // If we get here we need find and import the actual legacy elements using the current namespace. + // Either there is no old digest in the XMP, or the digests differ. In the former case keep any + // existing XMP, in the latter case take new legacy values. + this->containsXMP = XDCAM_Support::GetLegacyMetaData ( &this->xmpObj, rootElem, legacyNS, digestFound, thisUMID ); + + // If this clip is part of a take, add the take number to the relation field, and get the + // duration from the take metadata. + GetTakeUMID ( thisUMID, takeUMID, takeXMLURI ); + + // If this clip is part of a take, update the duration to reflect the take duration rather than + // the clip duration, and add the take name as a shot name. + + if ( ! takeXMLURI.empty() ) { + + // Update duration. This property already exists from clip legacy metadata. + GetTakeDuration ( takeXMLURI, takeDuration ); + if ( ! takeDuration.empty() ) { + this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", takeDuration ); + containsXMP = true; + } + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "shotName" )) ) { + + std::string takeName; + SplitLeafName ( &takeXMLURI, &takeName ); + + // Check for the xml suffix, and delete if it exists. + size_t pos = takeName.rfind(".SMI"); + if ( pos != std::string::npos ) { + + takeName.erase ( pos ); + + // delete the take number suffix if it exists. + if ( takeName.size() > 3 ) { + + size_t suffix = takeName.size() - 3; + char c1 = takeName[suffix]; + char c2 = takeName[suffix+1]; + char c3 = takeName[suffix+2]; + if ( ('U' == c1) && ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) { + takeName.erase ( suffix ); + } + + this->xmpObj.SetProperty ( kXMP_NS_DM, "shotName", takeName, kXMP_DeleteExisting ); + containsXMP = true; + + } + + } + + } + + } + + if ( (! takeUMID.empty()) && + (digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "relation" ))) ) { + this->xmpObj.DeleteProperty ( kXMP_NS_DC, "relation" ); + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, takeUMID ); + this->containsXMP = true; + } + + CleanupAndExit + #undef CleanupAndExit + +} // XDCAMEX_MetaHandler::ProcessXMP + + +// ================================================================================================= +// XDCAMEX_MetaHandler::UpdateFile +// =============================== +// +// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here. + +void XDCAMEX_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Make sure only called once. + + LFA_FileRef oldFile = 0; + std::string filePath, tempPath; + + // Update the internal legacy XML tree if we have one, and set the digest in the XMP. + + bool updateLegacyXML = false; + + if ( this->clipMetadata != 0 ) { + updateLegacyXML = XDCAM_Support::SetLegacyMetaData ( this->clipMetadata, &this->xmpObj, this->legacyNS.c_str()); + } + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAMEX", newDigest.c_str(), kXMP_DeleteExisting ); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() ); + + // Update the legacy XML file if necessary. + + if ( updateLegacyXML ) { + + std::string legacyXML; + this->expat->tree.Serialize ( &legacyXML ); + + this->MakeClipFilePath ( &filePath, "M01.XML" ); + oldFile = LFA_Open ( filePath.c_str(), 'w' ); + + if ( oldFile == 0 ) { + + // The XML does not exist yet. + + this->MakeClipFilePath ( &filePath, "M01.XML" ); + oldFile = LFA_Create ( filePath.c_str() ); + if ( oldFile == 0 ) XMP_Throw ( "Failure creating XDCAMEX legacy XML file", kXMPErr_ExternalFailure ); + LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() ); + LFA_Close ( oldFile ); + + } else if ( ! doSafeUpdate ) { + + // Over write the existing XML file. + + LFA_Seek ( oldFile, 0, SEEK_SET ); + LFA_Truncate ( oldFile, 0 ); + LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() ); + LFA_Close ( oldFile ); + + } else { + + // Do a safe update. + + // *** We really need an LFA_SwapFiles utility. + + this->MakeClipFilePath ( &filePath, "M01.XML" ); + + CreateTempFile ( filePath, &tempPath ); + LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' ); + LFA_Write ( tempFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() ); + LFA_Close ( tempFile ); + + LFA_Close ( oldFile ); + LFA_Delete ( filePath.c_str() ); + LFA_Rename ( tempPath.c_str(), filePath.c_str() ); + + } + + } + + oldFile = this->parent->fileRef; + + if ( oldFile == 0 ) { + + // The XMP does not exist yet. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); + + LFA_FileRef xmpFile = LFA_Create ( xmpPath.c_str() ); + if ( xmpFile == 0 ) XMP_Throw ( "Failure creating XDCAMEX XMP file", kXMPErr_ExternalFailure ); + LFA_Write ( xmpFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( xmpFile ); + + } else if ( ! doSafeUpdate ) { + + // Over write the existing XMP file. + + LFA_Seek ( oldFile, 0, SEEK_SET ); + LFA_Truncate ( oldFile, 0 ); + LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( oldFile ); + + } else { + + // Do a safe update. + + // *** We really need an LFA_SwapFiles utility. + + std::string xmpPath, tempPath; + + this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); + + CreateTempFile ( xmpPath, &tempPath ); + LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' ); + LFA_Write ( tempFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( tempFile ); + + LFA_Close ( oldFile ); + LFA_Delete ( xmpPath.c_str() ); + LFA_Rename ( tempPath.c_str(), xmpPath.c_str() ); + + } + + this->parent->fileRef = 0; + +} // XDCAMEX_MetaHandler::UpdateFile + +// ================================================================================================= +// XDCAMEX_MetaHandler::WriteFile +// ============================== + +void XDCAMEX_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) +{ + + // ! WriteFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "XDCAMEX_MetaHandler::WriteFile should not be called", kXMPErr_InternalFailure ); + +} // XDCAMEX_MetaHandler::WriteFile + +// ================================================================================================= diff --git a/source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp new file mode 100644 index 0000000..63852b5 --- /dev/null +++ b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp @@ -0,0 +1,81 @@ +#ifndef __XDCAMEX_Handler_hpp__ +#define __XDCAMEX_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. + +#include "XMPFiles_Impl.hpp" + +#include "ExpatAdapter.hpp" + +// ================================================================================================= +/// \file XDCAMEX_Handler.hpp +/// \brief Folder format handler for XDCAMEX. +// ================================================================================================= + +extern XMPFileHandler * XDCAMEX_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool XDCAMEX_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kXDCAMEX_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + +class XDCAMEX_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files. + { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); }; + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); + + XDCAMEX_MetaHandler ( XMPFiles * _parent ); + virtual ~XDCAMEX_MetaHandler(); + +private: + + XDCAMEX_MetaHandler() : expat(0) {}; // Hidden on purpose. + + void MakeClipFilePath ( std::string * path, XMP_StringPtr suffix ); + void MakeLegacyDigest ( std::string * digestStr ); + + void GetTakeUMID( const std::string& clipUMID, std::string& takeUMID, std::string& takeXMLURI ); + void GetTakeDuration( const std::string& takeUMID, std::string& duration ); + + void CleanupLegacyXML(); + + std::string rootPath, clipName, defaultNS, xdcNS, legacyNS, clipUMID; + + ExpatAdapter * expat; + XML_Node * clipMetadata; + +}; // XDCAMEX_MetaHandler + +// ================================================================================================= + +#endif /* __XDCAMEX_Handler_hpp__ */ diff --git a/source/XMPFiles/FileHandlers/XDCAM_Handler.cpp b/source/XMPFiles/FileHandlers/XDCAM_Handler.cpp new file mode 100644 index 0000000..6ccfcab --- /dev/null +++ b/source/XMPFiles/FileHandlers/XDCAM_Handler.cpp @@ -0,0 +1,726 @@ +// ================================================================================================= +// 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 "XDCAM_Handler.hpp" +#include "XDCAM_Support.hpp" +#include "MD5.h" + +using namespace std; + +// ================================================================================================= +/// \file XDCAM_Handler.cpp +/// \brief Folder format handler for XDCAM. +/// +/// This handler is for the XDCAM video format. This is a pseudo-package, visible files but with a very +/// well-defined layout and naming rules. There are 2 different layouts for XDCAM, called FAM and SAM. +/// The FAM layout is used by "normal" XDCAM devices. The SAM layout is used by XDCAM-EX devices. +/// +/// A typical FAM layout looks like: +/// +/// .../MyMovie/ +/// INDEX.XML +/// DISCMETA.XML +/// MEDIAPRO.XML +/// GENERAL/ +/// unknown files +/// CLIP/ +/// C0001.MXF +/// C0001M01.XML +/// C0001M01.XMP +/// C0002.MXF +/// C0002M01.XML +/// C0002M01.XMP +/// SUB/ +/// C0001S01.MXF +/// C0002S01.MXF +/// EDIT/ +/// E0001E01.SMI +/// E0001M01.XML +/// E0002E01.SMI +/// E0002M01.XML +/// +/// A typical SAM layout looks like: +/// +/// .../MyMovie/ +/// GENERAL/ +/// unknown files +/// PROAV/ +/// INDEX.XML +/// INDEX.BUP +/// DISCMETA.XML +/// DISCINFO.XML +/// DISCINFO.BUP +/// CLPR/ +/// C0001/ +/// C0001C01.SMI +/// C0001V01.MXF +/// C0001A01.MXF +/// C0001A02.MXF +/// C0001R01.BIM +/// C0001I01.PPN +/// C0001M01.XML +/// C0001M01.XMP +/// C0001S01.MXF +/// C0002/ +/// ... +/// EDTR/ +/// E0001/ +/// E0001E01.SMI +/// E0001M01.XML +/// E0002/ +/// ... +/// +/// Note that the Sony documentation uses the folder names "General", "Clip", "Sub", and "Edit". We +/// use all caps here. Common code has already shifted the names, we want to be case insensitive. +/// +/// From the user's point of view, .../MyMovie contains XDCAM stuff, in this case 2 clips whose raw +/// names are C0001 and C0002. There may be mapping information for nicer clip names to the raw +/// names, but that can be ignored for now. Each clip is stored as a collection of files, each file +/// holding some specific aspect of the clip's data. +/// +/// The XDCAM handler operates on clips. The path from the client of XMPFiles can be either a logical +/// clip path, like ".../MyMovie/C0001", or a full path to one of the files. In the latter case the +/// handler must figure out the intended clip, it must not blindly use the named file. +/// +/// Once the XDCAM structure and intended clip are identified, the handler only deals with the .XMP +/// and .XML files in the CLIP or CLPR/<clip> folders. The .XMP file, if present, contains the XMP +/// for the clip. The .XML file must be present to define the existance of the clip. It contains a +/// variety of information about the clip, including some legacy metadata. +/// +// ================================================================================================= + +// ================================================================================================= +// XDCAM_CheckFormat +// ================= +// +// This version does fairly simple checks. The top level folder (.../MyMovie) must have exactly 1 +// child, a folder called CONTENTS. This must have a subfolder called CLIP. It may also have +// subfolders called VIDEO, AUDIO, ICON, VOICE, and PROXY. Any mixture of these additional folders +// is allowed, but no other children are allowed in CONTENTS. The CLIP folder must contain a .XML +// file for the desired clip. The name checks are case insensitive. +// +// The state of the string parameters depends on the form of the path passed by the client. If the +// client passed a logical clip path, like ".../MyMovie/C0001", the parameters are: +// rootPath - ".../MyMovie" +// gpName - empty +// parentName - empty +// leafName - "C0001" +// +// If the client passed a FAM file path, like ".../MyMovie/EDIT/E0001E01.SMI", they are: +// rootPath - "..." +// gpName - "MyMovie" +// parentName - "EDIT" +// leafName - "E0001E01" +// +// If the client passed a SAM file path, like ".../MyMovie/PROAV/CLPR/C0001/C0001A02.MXF", they are: +// rootPath - ".../MyMovie/PROAV" +// gpName - "CLPR" +// parentName - "C0001" +// leafName - "C0001A02" +// +// For both FAM and SAM the leading character of the leafName for an existing file might be coerced +// to 'C' to form the logical clip name. And suffix such as "M01" must be removed for FAM. We don't +// need to worry about that for SAM, that uses the <clip> folder name. + +// ! The FAM format supports general clip file names through an ALIAS.XML mapping file. The simple +// ! existence check has an edge case bug, left to be fixed later. If the ALIAS.XML file exists, but +// ! some of the clips still have "raw" names, and we're passed an existing file path in the EDIT +// ! folder, we will fail to do the leading 'E' to 'C' coercion. We might also erroneously remove a +// ! suffix from a mapped essence file with a name like ClipX01.MXF. + +// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +bool XDCAM_CheckFormat ( XMP_FileFormat format, + const std::string & _rootPath, + const std::string & _gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ) +{ + std::string rootPath = _rootPath; // ! Need tweaking in the existing file cases (FAM and SAM). + std::string gpName = _gpName; + + bool isFAM = false; + + std::string tempPath, childName; + XMP_FolderInfo folderInfo; + + std::string clipName = leafName; + + // Do some basic checks on the root path and component names. Decide if this is FAM or SAM. + + if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty. + + if ( gpName.empty() ) { + + // This is the logical clip path case. Just look for PROAV to see if this is FAM or SAM. + if ( GetChildMode ( rootPath, "PROAV" ) != kFMode_IsFolder ) isFAM = true; + + } else { + + // This is the existing file case. See if this is FAM or SAM, tweak the clip name as needed. + + if ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) { + isFAM = true; + } else if ( (gpName != "CLPR") && (gpName != "EDTR") ) { + return false; + } + + if ( isFAM ) { + + // Put the proper root path together. Clean up the clip name if needed. + + if ( ! rootPath.empty() ) rootPath += kDirChar; + rootPath += gpName; + gpName.erase(); + + if ( GetChildMode ( rootPath, "ALIAS.XML" ) != kFMode_IsFile ) { + clipName[0] = 'C'; // ! See notes above about pending bug. + } + + if ( clipName.size() > 3 ) { + size_t clipMid = clipName.size() - 3; + char c1 = clipName[clipMid]; + char c2 = clipName[clipMid+1]; + char c3 = clipName[clipMid+2]; + if ( ('A' <= c1) && (c1 <= 'Z') && + ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) { + clipName.erase ( clipMid ); + } + } + + } else { + + // Fix the clip name. Check for and strip the "PROAV" suffix on the root path. + + clipName = parentName; // ! We have a folder with the (almost) exact clip name. + clipName[0] = 'C'; + + std::string proav; + SplitLeafName ( &rootPath, &proav ); + MakeUpperCase ( &proav ); + if ( (rootPath.empty()) || (proav != "PROAV") ) return false; + + } + + } + + // Make sure the general XDCAM package structure is legit. Set tempPath as a bogus path of the + // form <root>/<FAM-or-SAM>/<clip>, e.g. ".../MyMovie/FAM/C0001". This is passed the handler via + // the handlerTemp hackery. + + if ( isFAM ) { + + if ( (format != kXMP_XDCAM_FAMFile) && (format != kXMP_UnknownFile) ) return false; + + tempPath = rootPath; + + if ( GetChildMode ( tempPath, "INDEX.XML" ) != kFMode_IsFile ) return false; + if ( GetChildMode ( tempPath, "DISCMETA.XML" ) != kFMode_IsFile ) return false; + if ( GetChildMode ( tempPath, "MEDIAPRO.XML" ) != kFMode_IsFile ) return false; + + tempPath += kDirChar; + tempPath += "CLIP"; + tempPath += kDirChar; + tempPath += clipName; + tempPath += "M01.XML"; + if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false; + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += "FAM"; + tempPath += kDirChar; + tempPath += clipName; + + } else { + + if ( (format != kXMP_XDCAM_SAMFile) && (format != kXMP_UnknownFile) ) return false; + + // We already know about the PROAV folder, just check below it. + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += "PROAV"; + + if ( GetChildMode ( tempPath, "INDEX.XML" ) != kFMode_IsFile ) return false; + if ( GetChildMode ( tempPath, "DISCMETA.XML" ) != kFMode_IsFile ) return false; + if ( GetChildMode ( tempPath, "DISCINFO.XML" ) != kFMode_IsFile ) return false; + if ( GetChildMode ( tempPath, "CLPR" ) != kFMode_IsFolder ) return false; + + tempPath += kDirChar; + tempPath += "CLPR"; + tempPath += kDirChar; + tempPath += clipName; + if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFolder ) return false; + + tempPath += kDirChar; + tempPath += clipName; + tempPath += "M01.XML"; + if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false; + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += "SAM"; + tempPath += kDirChar; + tempPath += clipName; + + } + + // Save the pseudo-path for the handler object. A bit of a hack, but the only way to get info + // from here to there. + + size_t pathLen = tempPath.size() + 1; // Include a terminating nul. + parent->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. + + return true; + +} // XDCAM_CheckFormat + +// ================================================================================================= +// XDCAM_MetaHandlerCTor +// ===================== + +XMPFileHandler * XDCAM_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new XDCAM_MetaHandler ( parent ); + +} // XDCAM_MetaHandlerCTor + +// ================================================================================================= +// XDCAM_MetaHandler::XDCAM_MetaHandler +// ==================================== + +XDCAM_MetaHandler::XDCAM_MetaHandler ( XMPFiles * _parent ) : isFAM(false), expat(0) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kXDCAM_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + // Extract the root path, clip name, and FAM/SAM flag from handlerTemp. + + XMP_Assert ( this->parent->handlerTemp != 0 ); + + this->rootPath.assign ( (char*) this->parent->handlerTemp ); + free ( this->parent->handlerTemp ); + this->parent->handlerTemp = 0; + + SplitLeafName ( &this->rootPath, &this->clipName ); + + std::string temp; + SplitLeafName ( &this->rootPath, &temp ); + XMP_Assert ( (temp == "FAM") || (temp == "SAM") ); + if ( temp == "FAM" ) this->isFAM = true; + XMP_Assert ( this->isFAM ? (this->parent->format == kXMP_XDCAM_FAMFile) : (this->parent->format == kXMP_XDCAM_SAMFile) ); + +} // XDCAM_MetaHandler::XDCAM_MetaHandler + +// ================================================================================================= +// XDCAM_MetaHandler::~XDCAM_MetaHandler +// ===================================== + +XDCAM_MetaHandler::~XDCAM_MetaHandler() +{ + + this->CleanupLegacyXML(); + if ( this->parent->handlerTemp != 0 ) { + free ( this->parent->handlerTemp ); + this->parent->handlerTemp = 0; + } + +} // XDCAM_MetaHandler::~XDCAM_MetaHandler + +// ================================================================================================= +// XDCAM_MetaHandler::MakeClipFilePath +// =================================== + +void XDCAM_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix ) +{ + + *path = this->rootPath; + *path += kDirChar; + + if ( this->isFAM ) { + *path += "CLIP"; + } else { + *path += "PROAV"; + *path += kDirChar; + *path += "CLPR"; + *path += kDirChar; + *path += this->clipName; + } + + *path += kDirChar; + *path += this->clipName; + *path += suffix; + +} // XDCAM_MetaHandler::MakeClipFilePath + +// ================================================================================================= +// XDCAM_MetaHandler::MakeLegacyDigest +// =================================== + +// *** Early hack version. + +#define kHexDigits "0123456789ABCDEF" + +void XDCAM_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + digestStr->erase(); + if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML. + XMP_Assert ( this->expat != 0 ); + + XMP_StringPtr xdcNS = this->xdcNS.c_str(); + XML_NodePtr legacyContext, legacyProp; + + legacyContext = this->clipMetadata->GetNamedElement ( xdcNS, "Access" ); + if ( legacyContext == 0 ) return; + + MD5_CTX context; + unsigned char digestBin [16]; + MD5Init ( &context ); + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "Creator" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "CreationDate" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "LastUpdateDate" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + MD5Final ( digestBin, &context ); + + char buffer [40]; + for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digestBin[in]; + buffer[out] = kHexDigits [ byte >> 4 ]; + buffer[out+1] = kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + digestStr->append ( buffer ); + +} // XDCAM_MetaHandler::MakeLegacyDigest + +// ================================================================================================= +// P2_MetaHandler::CleanupLegacyXML +// ================================ + +void XDCAM_MetaHandler::CleanupLegacyXML() +{ + + if ( ! this->defaultNS.empty() ) { + SXMPMeta::DeleteNamespace ( this->defaultNS.c_str() ); + this->defaultNS.erase(); + } + + if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; } + + clipMetadata = 0; // ! Was a pointer into the expat tree. + +} // XDCAM_MetaHandler::CleanupLegacyXML + +// ================================================================================================= +// XDCAM_MetaHandler::CacheFileData +// ================================ + +void XDCAM_MetaHandler::CacheFileData() +{ + XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) ); + + // See if the clip's .XMP file exists. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); + if ( GetFileMode ( xmpPath.c_str() ) != kFMode_IsFile ) return; // No XMP. + + // Read the entire .XMP file. + + char openMode = 'r'; + if ( this->parent->openFlags & kXMPFiles_OpenForUpdate ) openMode = 'w'; + + LFA_FileRef xmpFile = LFA_Open ( xmpPath.c_str(), openMode ); + if ( xmpFile == 0 ) return; // The open failed. + + XMP_Int64 xmpLen = LFA_Measure ( xmpFile ); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "XDCAM XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.reserve ( (size_t)xmpLen ); + this->xmpPacket.append ( (size_t)xmpLen, ' ' ); + + XMP_Int32 ioCount = LFA_Read ( xmpFile, (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen, kLFA_RequireAll ); + XMP_Assert ( ioCount == xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + XMP_Assert ( this->parent->fileRef == 0 ); + if ( openMode == 'r' ) { + LFA_Close ( xmpFile ); + } else { + this->parent->fileRef = xmpFile; + } + + this->containsXMP = true; + +} // XDCAM_MetaHandler::CacheFileData + +// ================================================================================================= +// XDCAM_MetaHandler::ProcessXMP +// ============================= + +void XDCAM_MetaHandler::ProcessXMP() +{ + + // Some versions of gcc can't tolerate goto's across declarations. + // *** Better yet, avoid this cruft with self-cleaning objects. + #define CleanupAndExit \ + { \ + bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \ + if ( ! openForUpdate ) this->CleanupLegacyXML(); \ + return; \ + } + + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + // NonRealTimeMeta -> XMP by schema + std::string xmlPath, umid; + this->MakeClipFilePath ( &xmlPath, "M01.XML" ); + + // -------------------------------------------------------------- + // *** 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(); + if ( this->expat == 0 ) XMP_Throw ( "XDCAM_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory ); + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = LFA_Read ( xmlFile.fileRef, buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + this->expat->ParseBuffer ( 0, 0, true ); // End the parse. + + 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; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) { + rootElem = xmlTree.content[i]; + } + } + + if ( rootElem == 0 ) CleanupAndExit + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + if ( ! XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) CleanupAndExit + + this->legacyNS = rootElem->ns; + + // Check the legacy digest. + + XMP_StringPtr legacyNS = this->legacyNS.c_str(); + + this->clipMetadata = rootElem; // ! Save the NonRealTimeMeta pointer for other use. + + std::string oldDigest, newDigest; + bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAM", &oldDigest, 0 ); + if ( digestFound ) { + this->MakeLegacyDigest ( &newDigest ); + if ( oldDigest == newDigest ) CleanupAndExit + } + + // If we get here we need find and import the actual legacy elements using the current namespace. + // Either there is no old digest in the XMP, or the digests differ. In the former case keep any + // existing XMP, in the latter case take new legacy values. + + this->containsXMP = XDCAM_Support::GetLegacyMetaData ( &this->xmpObj, rootElem, legacyNS, digestFound, umid ); + + CleanupAndExit + #undef CleanupAndExit + +} // XDCAM_MetaHandler::ProcessXMP + +// ================================================================================================= +// XDCAM_MetaHandler::UpdateFile +// ============================= +// +// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here. + +void XDCAM_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Make sure only called once. + + LFA_FileRef oldFile = 0; + std::string filePath, tempPath; + + // Update the internal legacy XML tree if we have one, and set the digest in the XMP. + + bool updateLegacyXML = false; + + if ( this->clipMetadata != 0 ) { + updateLegacyXML = XDCAM_Support::SetLegacyMetaData ( this->clipMetadata, &this->xmpObj, this->legacyNS.c_str()); + } + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAM", newDigest.c_str(), kXMP_DeleteExisting ); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() ); + + // Update the legacy XML file if necessary. + + if ( updateLegacyXML ) { + + std::string legacyXML; + this->expat->tree.Serialize ( &legacyXML ); + + this->MakeClipFilePath ( &filePath, "M01.XML" ); + oldFile = LFA_Open ( filePath.c_str(), 'w' ); + + if ( oldFile == 0 ) { + + // The XML does not exist yet. + + this->MakeClipFilePath ( &filePath, "M01.XML" ); + oldFile = LFA_Create ( filePath.c_str() ); + if ( oldFile == 0 ) XMP_Throw ( "Failure creating XDCAMEX legacy XML file", kXMPErr_ExternalFailure ); + LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() ); + LFA_Close ( oldFile ); + + } else if ( ! doSafeUpdate ) { + + // Over write the existing XML file. + + LFA_Seek ( oldFile, 0, SEEK_SET ); + LFA_Truncate ( oldFile, 0 ); + LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() ); + LFA_Close ( oldFile ); + + } else { + + // Do a safe update. + + // *** We really need an LFA_SwapFiles utility. + + this->MakeClipFilePath ( &filePath, "M01.XML" ); + + CreateTempFile ( filePath, &tempPath ); + LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' ); + LFA_Write ( tempFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() ); + LFA_Close ( tempFile ); + + LFA_Close ( oldFile ); + LFA_Delete ( filePath.c_str() ); + LFA_Rename ( tempPath.c_str(), filePath.c_str() ); + + } + + } + + oldFile = this->parent->fileRef; + + if ( oldFile == 0 ) { + + // The XMP does not exist yet. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); + + LFA_FileRef xmpFile = LFA_Create ( xmpPath.c_str() ); + if ( xmpFile == 0 ) XMP_Throw ( "Failure creating XDCAM XMP file", kXMPErr_ExternalFailure ); + LFA_Write ( xmpFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( xmpFile ); + + } else if ( ! doSafeUpdate ) { + + // Over write the existing XMP file. + + LFA_Seek ( oldFile, 0, SEEK_SET ); + LFA_Truncate ( oldFile, 0 ); + LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( oldFile ); + + } else { + + // Do a safe update. + + // *** We really need an LFA_SwapFiles utility. + + std::string xmpPath, tempPath; + + this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); + + CreateTempFile ( xmpPath, &tempPath ); + LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' ); + LFA_Write ( tempFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() ); + LFA_Close ( tempFile ); + + LFA_Close ( oldFile ); + LFA_Delete ( xmpPath.c_str() ); + LFA_Rename ( tempPath.c_str(), xmpPath.c_str() ); + + } + + this->parent->fileRef = 0; + +} // XDCAM_MetaHandler::UpdateFile + +// ================================================================================================= +// XDCAM_MetaHandler::WriteFile +// ============================ + +void XDCAM_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ) +{ + + // ! WriteFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "XDCAM_MetaHandler::WriteFile should not be called", kXMPErr_InternalFailure ); + +} // XDCAM_MetaHandler::WriteFile + +// ================================================================================================= diff --git a/source/XMPFiles/FileHandlers/XDCAM_Handler.hpp b/source/XMPFiles/FileHandlers/XDCAM_Handler.hpp new file mode 100644 index 0000000..abd861b --- /dev/null +++ b/source/XMPFiles/FileHandlers/XDCAM_Handler.hpp @@ -0,0 +1,82 @@ +#ifndef __XDCAM_Handler_hpp__ +#define __XDCAM_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. + +#include "XMPFiles_Impl.hpp" + +#include "ExpatAdapter.hpp" + +// ================================================================================================= +/// \file XDCAM_Handler.hpp +/// \brief Folder format handler for XDCAM. +/// +/// This header ... +/// +// ================================================================================================= + +extern XMPFileHandler * XDCAM_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool XDCAM_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kXDCAM_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + +class XDCAM_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files. + { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); }; + + void UpdateFile ( bool doSafeUpdate ); + void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath ); + + XDCAM_MetaHandler ( XMPFiles * _parent ); + virtual ~XDCAM_MetaHandler(); + +private: + + XDCAM_MetaHandler() : isFAM(false), expat(0), clipMetadata(0) {}; // Hidden on purpose. + + void MakeClipFilePath ( std::string * path, XMP_StringPtr suffix ); + void MakeLegacyDigest ( std::string * digestStr ); + void CleanupLegacyXML(); + + std::string rootPath, clipName, defaultNS, xdcNS, legacyNS; + + bool isFAM; + + ExpatAdapter * expat; + XML_Node * clipMetadata; // ! Don't delete, points into the Expat tree. + +}; // XDCAM_MetaHandler + +// ================================================================================================= + +#endif /* __XDCAM_Handler_hpp__ */ |