diff options
Diffstat (limited to 'XMPFiles/source/FileHandlers/XDCAM_Handler.cpp')
-rw-r--r-- | XMPFiles/source/FileHandlers/XDCAM_Handler.cpp | 837 |
1 files changed, 837 insertions, 0 deletions
diff --git a/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp b/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp new file mode 100644 index 0000000..fab0925 --- /dev/null +++ b/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp @@ -0,0 +1,837 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/XDCAM_Handler.hpp" +#include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp" +#include "third-party/zuid/interfaces/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 (note mixed case for General, Clip, Edit, and Sub folders): +/// +/// .../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" (common code has shifted the case) +// leafName - "E0001E01" +// +// If the client passed a SAM file path, like ".../MyMovie/PROAV/CLPR/C0001/C0001A02.MXF", they are: +// 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 uppercase. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +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; + + 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 ( Host_IO::GetChildMode ( rootPath.c_str(), "PROAV" ) != Host_IO::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") ) { + // ! The standard says Clip/Edit/Sub, but the caller has already shifted to upper case. + isFAM = true; + } else if ( (gpName != "CLPR") && (gpName != "EDTR") ) { + return false; + } + + 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 ( Host_IO::GetChildMode ( rootPath.c_str(), "ALIAS.XML" ) != Host_IO::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; + XIO::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 tempPtr hackery. + + if ( isFAM ) { + + if ( (format != kXMP_XDCAM_FAMFile) && (format != kXMP_UnknownFile) ) return false; + + tempPath = rootPath; + + if ( Host_IO::GetChildMode ( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCMETA.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode ( tempPath.c_str(), "MEDIAPRO.XML" ) != Host_IO::kFMode_IsFile ) return false; + + tempPath += kDirChar; + tempPath += "Clip"; // ! Yes, mixed case. + tempPath += kDirChar; + tempPath += clipName; + tempPath += "M01.XML"; + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::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 ( Host_IO::GetChildMode ( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCMETA.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCINFO.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode ( tempPath.c_str(), "CLPR" ) != Host_IO::kFMode_IsFolder ) return false; + + tempPath += kDirChar; + tempPath += "CLPR"; + tempPath += kDirChar; + tempPath += clipName; + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFolder ) return false; + + tempPath += kDirChar; + tempPath += clipName; + tempPath += "M01.XML"; + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::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->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for XDCAM clip info", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. + + return true; + +} // XDCAM_CheckFormat + +// ================================================================================================= + +static void* CreatePseudoClipPath ( const std::string & clientPath ) { + + // Used to create the clip pseudo path when the CheckFormat function is skipped. + + std::string pseudoPath = clientPath; + std::string clipName; + bool isSAM; + + size_t pathLen; + void* tempPtr = 0; + + if ( ! Host_IO::Exists ( pseudoPath.c_str() ) ) { + + // This is the logical clip path case. Look for PROAV to see if this is FAM or SAM. + + XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name, no extension. + isSAM = ( Host_IO::GetChildMode ( pseudoPath.c_str(), "PROAV" ) == Host_IO::kFMode_IsFolder ); + + } else { + + // The client passed a physical path. We have separate cases for FAM and SAM. If the last + // folder, the parent of the file, is Clip, Edit, or Sub (ignoring case) then this is FAM + // and things are a bit messy. For SAM, the parent folder is the almost clip name. + + std::string parentName, ignored; + + XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name. + XIO::SplitFileExtension ( &clipName, &ignored ); + + XIO::SplitLeafName ( &pseudoPath, &parentName ); + MakeUpperCase ( &parentName ); + isSAM = ( (parentName != "CLIP") && (parentName != "EDIT") && (parentName != "SUB") ); + + if ( isSAM ) { + + // SAM is easy, the parent name is almost the clip name, the first letter gets coerced + // to 'C'. There are 2 other folders to remove from the path. + + clipName = parentName; + clipName[0] = 'C'; + XIO::SplitLeafName ( &pseudoPath, &ignored ); // Remove the 2 intermediate folder levels. + XIO::SplitLeafName ( &pseudoPath, &ignored ); + + } else { + + // FAM is a bit messy, study the comments and code of XDCAM_CheckFormat for details. + + if ( Host_IO::GetChildMode ( pseudoPath.c_str(), "ALIAS.XML" ) != Host_IO::kFMode_IsFile ) { + clipName[0] = 'C'; // ! See notes in XDCAM_CheckFormat 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 ); + } + } + + } + + } + + pseudoPath += kDirChar; + if ( isSAM ) { + pseudoPath += "SAM"; + } else { + pseudoPath += "FAM"; + } + pseudoPath += kDirChar; + pseudoPath += clipName; + + pathLen = pseudoPath.size() + 1; // Include a terminating nul. + tempPtr = malloc ( pathLen ); + if ( tempPtr == 0 ) XMP_Throw ( "No memory for XDCAM clip info", kXMPErr_NoMemory ); + memcpy ( tempPtr, pseudoPath.c_str(), pathLen ); + + return tempPtr; + +} // CreatePseudoClipPath + +// ================================================================================================= +// 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 tempPtr. + + if ( this->parent->tempPtr == 0 ) { + // The CheckFormat call might have been skipped. + this->parent->tempPtr = CreatePseudoClipPath ( this->parent->filePath ); + } + + this->rootPath.assign ( (char*) this->parent->tempPtr ); + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + + XIO::SplitLeafName ( &this->rootPath, &this->clipName ); + + std::string temp; + XIO::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->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + } + +} // XDCAM_MetaHandler::~XDCAM_MetaHandler + +// ================================================================================================= +// XDCAM_MetaHandler::MakeClipFilePath +// =================================== + +bool XDCAM_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) +{ + + *path = this->rootPath; + *path += kDirChar; + + if ( this->isFAM ) { + *path += "Clip"; // ! Yes, mixed case. + } else { + *path += "PROAV"; + *path += kDirChar; + *path += "CLPR"; + *path += kDirChar; + *path += this->clipName; + } + + *path += kDirChar; + *path += this->clipName; + *path += suffix; + + if ( ! checkFile ) return true; + return Host_IO::Exists ( path->c_str() ); + +} // XDCAM_MetaHandler::MakeClipFilePath + +// ================================================================================================= +// XDCAM_MetaHandler::MakeMediaproPath +// =================================== + +bool XDCAM_MetaHandler::MakeMediaproPath ( std::string * path, bool checkFile /* = false */ ) +{ + + *path = this->rootPath; + *path += kDirChar; + *path += "MEDIAPRO.XML"; + + if ( ! checkFile ) return true; + return Host_IO::Exists ( path->c_str() ); + +} // XDCAM_MetaHandler::MakeMediaproPath + +// ================================================================================================= +// 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->expat != 0 ) { delete ( this->expat ); this->expat = 0; } + + clipMetadata = 0; // ! Was a pointer into the expat tree. + +} // XDCAM_MetaHandler::CleanupLegacyXML + +// ================================================================================================= +// XDCAM_MetaHandler::GetFileModDate +// ================================= + +static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) { + int compare = SXMPUtils::CompareDateTime ( left, right ); + return (compare < 0); +} + +bool XDCAM_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) +{ + + // The XDCAM FAM locations of metadata: + // MEDIAPRO.XML // Has non-XMP metadata. + // Clip: + // C0001_50i_DVCAM_43_4chM01.XML // Has non-XMP metadata. + // C0001_50i_DVCAM_43_4chM01.XMP + + // The XDCAM SAM locations of metadata: + // PROAV: + // CLPR: + // C0001: + // C0001M01.XML // Has non-XMP metadata. + // C0001M01.XMP + + bool ok, haveDate = false; + std::string fullPath; + XMP_DateTime oneDate, junkDate; + if ( modDate == 0 ) modDate = &junkDate; + + std::string mediaproPath; + ok = MakeMediaproPath ( &mediaproPath, true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( mediaproPath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate; + haveDate = true; + } + + ok = this->MakeClipFilePath ( &fullPath, "M01.XML", true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate; + haveDate = true; + } + + ok = this->MakeClipFilePath ( &fullPath, "M01.XMP", true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate; + haveDate = true; + } + + return haveDate; + +} // XDCAM_MetaHandler::GetFileModDate + +// ================================================================================================= +// XDCAM_MetaHandler::CacheFileData +// ================================ + +void XDCAM_MetaHandler::CacheFileData() +{ + XMP_Assert ( ! this->containsXMP ); + + if ( this->parent->UsesClientIO() ) { + XMP_Throw ( "XDCAM cannot be used with client-managed I/O", kXMPErr_InternalFailure ); + } + + // See if the clip's .XMP file exists. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); + if ( Host_IO::GetFileMode ( xmpPath.c_str() ) != Host_IO::kFMode_IsFile ) return; // No XMP. + + // Read the entire .XMP file. + + bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); + + XMP_Assert ( this->parent->ioRef == 0 ); + XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly ); + if ( xmpFile == 0 ) return; // The open failed. + this->parent->ioRef = xmpFile; + + XMP_Int64 xmpLen = xmpFile->Length(); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "XDCAM XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.append ( (size_t)xmpLen, ' ' ); + + XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + this->containsXMP = true; + +} // XDCAM_MetaHandler::CacheFileData + +// ================================================================================================= +// XDCAM_MetaHandler::GetMediaProMetadata +// ====================================== + +bool XDCAM_MetaHandler::GetMediaProMetadata ( SXMPMeta * xmpObjPtr, + const std::string& clipUMID, + bool digestFound ) +{ + if (!this->isFAM) return false; + + // Build a directory string to the MEDIAPRO file. + + std::string mediaproPath; + MakeMediaproPath ( &mediaproPath ); + return XDCAM_Support::GetMediaProLegacyMetadata ( xmpObjPtr, clipUMID, mediaproPath, digestFound ); + +} + +// ================================================================================================= +// 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" ); + + Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return; // The open failed. + XMPFiles_IO xmlFile ( hostRef, xmlPath.c_str(), Host_IO::openReadOnly ); + + this->expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); + if ( this->expat == 0 ) XMP_Throw ( "XDCAM_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory ); + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = xmlFile.Read ( buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + this->expat->ParseBuffer ( 0, 0, true ); // End the parse. + + xmlFile.Close(); + + // 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 ); + this->containsXMP |= GetMediaProMetadata ( &this->xmpObj, umid, digestFound ); + + 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. + + XMP_Assert ( this->parent->UsesLocalIO() ); + + // 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 XMP file first, don't let legacy XML failures block the XMP. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); + + bool haveXMP = Host_IO::Exists ( xmpPath.c_str() ); + if ( ! haveXMP ) { + XMP_Assert ( this->parent->ioRef == 0 ); + Host_IO::Create ( xmpPath.c_str() ); + this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite ); + if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening XDCAM XMP file", kXMPErr_ExternalFailure ); + } + + XMP_IO* xmpFile = this->parent->ioRef; + XMP_Assert ( xmpFile != 0 ); + XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) ); + + // -------------------------------------------- + // Now update the legacy XML file if necessary. + + if ( updateLegacyXML ) { + + std::string legacyXML, xmlPath; + this->expat->tree.Serialize ( &legacyXML ); + this->MakeClipFilePath ( &xmlPath, "M01.XML" ); + + bool haveXML = Host_IO::Exists ( xmlPath.c_str() ); + if ( ! haveXML ) Host_IO::Create ( xmlPath.c_str() ); + + Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadWrite ); + if ( hostRef == Host_IO::noFileRef ) XMP_Throw ( "Failure opening XDCAM XML file", kXMPErr_ExternalFailure ); + XMPFiles_IO origXML ( hostRef, xmlPath.c_str(), Host_IO::openReadWrite ); + XIO::ReplaceTextFile ( &origXML, legacyXML, (haveXML & doSafeUpdate) ); + origXML.Close(); + + } + +} // XDCAM_MetaHandler::UpdateFile + +// ================================================================================================= +// XDCAM_MetaHandler::WriteTempFile +// ================================ + +void XDCAM_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + + // ! WriteTempFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "XDCAM_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure ); + +} // XDCAM_MetaHandler::WriteTempFile + +// ================================================================================================= |