summaryrefslogtreecommitdiff
path: root/source/XMPFiles/FileHandlers
diff options
context:
space:
mode:
Diffstat (limited to 'source/XMPFiles/FileHandlers')
-rw-r--r--source/XMPFiles/FileHandlers/ASF_Handler.cpp362
-rw-r--r--source/XMPFiles/FileHandlers/ASF_Handler.hpp66
-rw-r--r--source/XMPFiles/FileHandlers/AVCHD_Handler.cpp648
-rw-r--r--source/XMPFiles/FileHandlers/AVCHD_Handler.hpp77
-rw-r--r--source/XMPFiles/FileHandlers/AVI_Handler.cpp148
-rw-r--r--source/XMPFiles/FileHandlers/Basic_Handler.cpp4
-rw-r--r--source/XMPFiles/FileHandlers/Basic_Handler.hpp1
-rw-r--r--source/XMPFiles/FileHandlers/FLV_Handler.cpp750
-rw-r--r--source/XMPFiles/FileHandlers/FLV_Handler.hpp73
-rw-r--r--source/XMPFiles/FileHandlers/InDesign_Handler.cpp22
-rw-r--r--source/XMPFiles/FileHandlers/JPEG_Handler.cpp59
-rw-r--r--source/XMPFiles/FileHandlers/MOV_Handler.cpp696
-rw-r--r--source/XMPFiles/FileHandlers/MOV_Handler.hpp20
-rw-r--r--source/XMPFiles/FileHandlers/MP3_Handler.cpp27
-rw-r--r--source/XMPFiles/FileHandlers/MP3_Handler.hpp4
-rw-r--r--source/XMPFiles/FileHandlers/MPEG2_Handler.cpp (renamed from source/XMPFiles/FileHandlers/MPEG_Handler.cpp)88
-rw-r--r--source/XMPFiles/FileHandlers/MPEG2_Handler.hpp (renamed from source/XMPFiles/FileHandlers/MPEG_Handler.hpp)38
-rw-r--r--source/XMPFiles/FileHandlers/MPEG4_Handler.cpp909
-rw-r--r--source/XMPFiles/FileHandlers/MPEG4_Handler.hpp69
-rw-r--r--source/XMPFiles/FileHandlers/P2_Handler.cpp1203
-rw-r--r--source/XMPFiles/FileHandlers/P2_Handler.hpp106
-rw-r--r--source/XMPFiles/FileHandlers/PNG_Handler.cpp6
-rw-r--r--source/XMPFiles/FileHandlers/PSD_Handler.cpp29
-rw-r--r--source/XMPFiles/FileHandlers/PostScript_Handler.cpp8
-rw-r--r--source/XMPFiles/FileHandlers/SWF_Handler.cpp397
-rw-r--r--source/XMPFiles/FileHandlers/SWF_Handler.hpp65
-rw-r--r--source/XMPFiles/FileHandlers/Scanner_Handler.cpp8
-rw-r--r--source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp782
-rw-r--r--source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp77
-rw-r--r--source/XMPFiles/FileHandlers/TIFF_Handler.cpp45
-rw-r--r--source/XMPFiles/FileHandlers/UCF_Handler.cpp846
-rw-r--r--source/XMPFiles/FileHandlers/UCF_Handler.hpp716
-rw-r--r--source/XMPFiles/FileHandlers/WAV_Handler.cpp109
-rw-r--r--source/XMPFiles/FileHandlers/WAV_Handler.hpp8
-rw-r--r--source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp824
-rw-r--r--source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp81
-rw-r--r--source/XMPFiles/FileHandlers/XDCAM_Handler.cpp726
-rw-r--r--source/XMPFiles/FileHandlers/XDCAM_Handler.hpp82
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__ */