// ================================================================================================= // ADOBE SYSTEMS INCORPORATED // Copyright 2008 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= #include "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 "source/IOUtils.hpp" #include "XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp" #include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp" #include "third-party/zuid/interfaces/MD5.h" #include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" 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 ( Host_IO::GetChildMode ( bpavPath.c_str(), "CLPR" ) != Host_IO::kFMode_IsFolder ) return false; } else { // This is the explicit file case. Make sure the ancestry is OK, compare using the parent's // length since the file can have a suffix like "M01". Use the leafName as the clipName to // preserve lower case, but truncate to the parent's length to remove any suffix. if ( gpName != "CLPR" ) return false; XIO::SplitLeafName ( &rootPath, &grandGPName ); MakeUpperCase ( &grandGPName ); if ( grandGPName != "BPAV" ) return false; if ( ! XMP_LitNMatch ( parentName.c_str(), clipName.c_str(), parentName.size() ) ) { std::string tempName = clipName; MakeUpperCase ( &tempName ); if ( ! XMP_LitNMatch ( parentName.c_str(), tempName.c_str(), parentName.size() ) ) return false; } clipName.erase ( parentName.size() ); } // Check the rest of the required general structure. if ( Host_IO::GetChildMode ( bpavPath.c_str(), "TAKR" ) != Host_IO::kFMode_IsFolder ) return false; if ( Host_IO::GetChildMode ( bpavPath.c_str(), "MEDIAPRO.XML" ) != Host_IO::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 ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false; tempPath.erase ( tempPath.size()-3 ); tempPath += "SMI"; if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::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->tempPtr = malloc ( pathLen ); if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory ); memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); return true; } // XDCAMEX_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; size_t pathLen; void* tempPtr = 0; if ( Host_IO::Exists ( pseudoPath.c_str() ) ) { // The client passed a physical path. The logical clip name is the last folder name, the // parent of the file. This is best since some files have suffixes. std::string clipName, ignored; XIO::SplitLeafName ( &pseudoPath, &ignored ); // Split the file name. XIO::SplitLeafName ( &pseudoPath, &clipName ); // Use the parent folder name. XIO::SplitLeafName ( &pseudoPath, &ignored ); // Remove the 2 intermediate folder levels. XIO::SplitLeafName ( &pseudoPath, &ignored ); pseudoPath += kDirChar; pseudoPath += clipName; } pathLen = pseudoPath.size() + 1; // Include a terminating nul. tempPtr = malloc ( pathLen ); if ( tempPtr == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory ); memcpy ( tempPtr, pseudoPath.c_str(), pathLen ); return tempPtr; } // CreatePseudoClipPath // ================================================================================================= // 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),clipMetadata(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 tempPtr. if ( this->parent->tempPtr == 0 ) { // The CheckFormat call might have been skipped. this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() ); } this->rootPath.assign ( (char*) this->parent->tempPtr ); free ( this->parent->tempPtr ); this->parent->tempPtr = 0; XIO::SplitLeafName ( &this->rootPath, &this->clipName ); } // XDCAMEX_MetaHandler::XDCAMEX_MetaHandler // ================================================================================================= // XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler // ========================================= XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler() { this->CleanupLegacyXML(); if ( this->parent->tempPtr != 0 ) { free ( this->parent->tempPtr ); this->parent->tempPtr = 0; } } // XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler // ================================================================================================= // XDCAMEX_MetaHandler::MakeClipFilePath // ===================================== bool XDCAMEX_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) { *path = this->rootPath; *path += kDirChar; *path += "BPAV"; *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() ); } // XDCAMEX_MetaHandler::MakeClipFilePath // ================================================================================================= // XDCAMEX_MetaHandler::MakeMediaproPath // ===================================== bool XDCAMEX_MetaHandler::MakeMediaproPath ( std::string * path, bool checkFile /* = false */ ) { *path = this->rootPath; *path += kDirChar; *path += "BPAV"; *path += kDirChar; *path += "MEDIAPRO.XML"; if ( ! checkFile ) return true; return Host_IO::Exists ( path->c_str() ); } // XDCAMEX_MetaHandler::MakeMediaproPath // ================================================================================================= // 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() { delete this->expat; this->expat = 0; clipMetadata = 0; // ! Was a pointer into the expat tree. } // XDCAMEX_MetaHandler::CleanupLegacyXML // ================================================================================================= // XDCAMEX_MetaHandler::GetFileModDate // =================================== static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) { int compare = SXMPUtils::CompareDateTime ( left, right ); return (compare < 0); } bool XDCAMEX_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) { // The XDCAM EX locations of metadata: // BPAV/ // MEDIAPRO.XML // Has non-XMP metadata. // CLPR/ // 709_3001_01: // 709_3001_01M01.XML // Has non-XMP metadata. // 709_3001_01M01.XMP bool ok, haveDate = false; std::string fullPath; XMP_DateTime oneDate, junkDate; if ( modDate == 0 ) modDate = &junkDate; ok = this->MakeMediaproPath ( &fullPath, true /* checkFile */ ); if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); if ( ok ) { if ( *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; } // XDCAMEX_MetaHandler::GetFileModDate // Adds all the associated resources for the specified clip only (not related spanned ones) static void FillClipAssociatedResources( std::vector * resourceList, std::string &clipPath, std::string &clipName ) { std::string filePath; std::string spannedClipFolderPath = clipPath + clipName + kDirChar; std::string clipPathNoExt = spannedClipFolderPath + clipName; // Get the files present inside clip folder. std::vector regExpStringVec; std::string regExpString; regExpString = "^" + clipName + ".MP4$"; regExpStringVec.push_back(regExpString); regExpString = "^" + clipName + "M\\d\\d.XMP$"; regExpStringVec.push_back(regExpString); regExpString = "^" + clipName + "M\\d\\d.XML$"; regExpStringVec.push_back(regExpString); regExpString = "^" + clipName + "I\\d\\d.PPN$"; regExpStringVec.push_back(regExpString); regExpString = "^" + clipName + "R\\d\\d.BIM$"; regExpStringVec.push_back(regExpString); regExpString = "^" + clipName + ".SMI$"; regExpStringVec.push_back(regExpString); IOUtils::GetMatchingChildren (*resourceList, spannedClipFolderPath, regExpStringVec, false, true, true ); } // ================================================================================================= // XDCAMEX_MetaHandler::FillAssociatedResources // ====================================== void XDCAMEX_MetaHandler::FillAssociatedResources ( std::vector * resourceList ) { // The possible associated resources: // BPAV/ // MEDIAPRO.XML // CUEUP.XML // CLPR/ // MIXXXX_YY: MI is MachineID, XXXX is TakeSerial, // YY is ClipSuffix(as single take can be divided across multiple clips.) // In case of spanning, all the clip folders starting from "MIXXXX_" are looked for. // MIXXXX_YY.MP4 // MIXXXX_YYMNN.XML NN is a counter which will start from from 01 and can go upto 99 based // on number of files present in this folder with same extension. // MIXXXX_YYMNN.XMP // MIXXXX_YYINN.PPN // MIXXXX_YYRNN.BIM // MXXXX_YY.SMI // TAKR/ // MIXXXX: // MIXXXXMNN.XML NN is a counter which will start from from 01 and can go upto 99 based // on number of files present in this folder with same extension. // MIXXXX.SMI // MIXXXXUNN.SMI NN is a counter which goes from 01 to N-1 where N is number of media, this // take is divided into. For Nth, MIXXXX.SMI shall be picked up. XMP_VarString bpavPath = this->rootPath + kDirChar + "BPAV" + kDirChar; XMP_VarString filePath; //Add RootPath filePath = this->rootPath + kDirChar; PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); // Get the files present directly inside BPAV folder. filePath = bpavPath + "MEDIAPRO.XML"; PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); filePath = bpavPath + "MEDIAPRO.BUP"; PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); filePath = bpavPath + "CUEUP.XML"; PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); filePath = bpavPath + "CUEUP.BUP"; PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); XMP_VarString clipPath = bpavPath + "CLPR" + kDirChar; size_t clipSuffixIndex = this->clipName.find_last_of('_'); XMP_VarString takeName = this->clipName.substr(0, clipSuffixIndex); // Add spanned clip files. // Here, we iterate over all the folders present inside "/BPAV/CLPR/" and whose name starts from // "MIXXXX_". All valid files present inside such folders are added to the list. XMP_VarString regExpString; regExpString = "^" + takeName + "_\\d\\d$"; XMP_StringVector list; IOUtils::GetMatchingChildren ( list, clipPath, regExpString, true, false, false ); size_t spaningClipsCount = list.size(); for ( size_t index = 0; index < spaningClipsCount; index++ ) { FillClipAssociatedResources ( resourceList, clipPath, list[index] ); } list.clear(); size_t sizeWithoutTakeFiles = resourceList->size(); XMP_VarString takeFolderPath = bpavPath + "TAKR" + kDirChar + takeName + kDirChar; XMP_StringVector regExpStringVec; // Get the files present inside take folder. regExpString = "^" + takeName + "M\\d\\d.XML$"; regExpStringVec.push_back ( regExpString ); regExpString = "^" + takeName + "U\\d\\d.SMI$"; regExpStringVec.push_back ( regExpString ); regExpString = "^" + takeName + ".SMI$"; regExpStringVec.push_back ( regExpString ); IOUtils::GetMatchingChildren ( *resourceList, takeFolderPath, regExpStringVec, false, true, true ); if ( sizeWithoutTakeFiles == resourceList->size() ) { // no Take files added to resource list. But "TAKR" folder is necessary to recognize this format // so let's add it to the list. filePath = bpavPath + "TAKR" + kDirChar; PackageFormat_Support::AddResourceIfExists(resourceList, filePath); } } // XDCAMEX_MetaHandler::FillAssociatedResources // ================================================================================================= // XDCAMEX_MetaHandler::FillMetadataFiles // ====================================== void XDCAMEX_MetaHandler::FillMetadataFiles ( std::vector * metadataFiles ) { std::string noExtPath, filePath; noExtPath = rootPath + kDirChar + "BPAV" + kDirChar + "CLPR" + kDirChar + clipName + kDirChar + clipName; filePath = noExtPath + "M01.XMP"; metadataFiles->push_back ( filePath ); filePath = noExtPath + "M01.XML"; metadataFiles->push_back ( filePath ); filePath = rootPath + kDirChar + "BPAV" + kDirChar + "MEDIAPRO.XML"; metadataFiles->push_back ( filePath ); } // FillMetadataFiles_XDCAM_EX // ================================================================================================= // XDCAMEX_MetaHandler::IsMetadataWritable // ======================================= bool XDCAMEX_MetaHandler::IsMetadataWritable ( ) { std::vector metadataFiles; FillMetadataFiles(&metadataFiles); std::vector::iterator itr = metadataFiles.begin(); // Check whether sidecar is writable, if not then check if it can be created. XMP_Bool xmpWritable = Host_IO::Writable( itr->c_str(), true ); // Check for legacy metadata file. XMP_Bool xmlWritable = Host_IO::Writable( (++itr)->c_str(), false ); return ( xmlWritable && xmpWritable ); }// XDCAMEX_MetaHandler::IsMetadataWritable // ================================================================================================= // XDCAMEX_MetaHandler::CacheFileData // ================================== void XDCAMEX_MetaHandler::CacheFileData() { XMP_Assert ( ! this->containsXMP ); if ( this->parent->UsesClientIO() ) { XMP_Throw ( "XDCAMEX 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::Exists ( xmpPath.c_str() ) ) return; // No XMP. // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 // only if the file does not exist. 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 ) XMP_Throw ( "XDCAMEX XMP file open failure", kXMPErr_InternalFailure ); this->parent->ioRef = xmpFile; XMP_Int64 xmpLen = xmpFile->Length(); if ( xmpLen > 100*1024*1024 ) { XMP_Throw ( "XDCAMEX 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; } // 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 \ { \ delete expatMediaPro; \ takeXMLFile.Close(); \ 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; Host_IO::FileRef hostRef = Host_IO::Open ( takePath.c_str(), Host_IO::openReadOnly ); if ( hostRef == Host_IO::noFileRef ) return; // The open failed. XMPFiles_IO takeXMLFile ( hostRef, takePath.c_str(), Host_IO::openReadOnly ); ExpatAdapter * expatMediaPro = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); if ( expatMediaPro == 0 ) return; XMP_Uns8 buffer [64*1024]; while ( true ) { XMP_Int32 ioCount = takeXMLFile.Read ( buffer, sizeof(buffer) ); if ( ioCount == 0 ) break; expatMediaPro->ParseBuffer ( buffer, ioCount, false /* not the end */ ); } expatMediaPro->ParseBuffer ( 0, 0, true ); // End the parse. takeXMLFile.Close(); // Get the root node of the XML tree. XML_Node & mediaproXMLTree = expatMediaPro->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::GetTakeDuration // ================================================================================================= // XDCAMEX_MetaHandler::GetMediaProMetadata // ======================================== bool XDCAMEX_MetaHandler::GetMediaProMetadata ( SXMPMeta * xmpObjPtr, const std::string& clipUMID, bool digestFound ) { // Build a directory string to the MEDIAPRO file. std::string mediaproPath; this->MakeMediaproPath ( &mediaproPath ); return XDCAM_Support::GetMediaProLegacyMetadata ( xmpObjPtr, clipUMID, mediaproPath, digestFound ); } // XDCAMEX_MetaHandler::GetMediaProMetadata // ================================================================================================= // 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 \ { \ delete expatMediaPro; \ mediaproXMLFile.Close(); \ 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; Host_IO::FileRef hostRef = Host_IO::Open ( mediapropath.c_str(), Host_IO::openReadOnly ); if ( hostRef == Host_IO::noFileRef ) return; // The open failed. XMPFiles_IO mediaproXMLFile ( hostRef, mediapropath.c_str(), Host_IO::openReadOnly ); ExpatAdapter * expatMediaPro = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); if ( expatMediaPro == 0 ) return; XMP_Uns8 buffer [64*1024]; while ( true ) { XMP_Int32 ioCount = mediaproXMLFile.Read ( buffer, sizeof(buffer) ); if ( ioCount == 0 ) break; expatMediaPro->ParseBuffer ( buffer, ioCount, false /* not the end */ ); } expatMediaPro->ParseBuffer ( 0, 0, true ); // End the parse. mediaproXMLFile.Close(); // Get the root node of the XML tree. XML_Node & mediaproXMLTree = expatMediaPro->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(); \ xmlFile.Close(); \ 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" ); 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 ( "XDCAMEX_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, "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; XIO::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; } this->containsXMP |= GetMediaProMetadata ( &this->xmpObj, thisUMID, digestFound ); 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. 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, "XDCAMEX", 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 XDCAMEX 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 XDCAMEX legacy XML file", kXMPErr_ExternalFailure ); XMPFiles_IO origXML ( hostRef, xmlPath.c_str(), Host_IO::openReadWrite ); XIO::ReplaceTextFile ( &origXML, legacyXML, (haveXML & doSafeUpdate) ); origXML.Close(); } } // XDCAMEX_MetaHandler::UpdateFile // ================================================================================================= // XDCAMEX_MetaHandler::WriteTempFile // ================================== void XDCAMEX_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) { // ! WriteTempFile is not supposed to be called for handlers that own the file. XMP_Throw ( "XDCAMEX_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure ); } // XDCAMEX_MetaHandler::WriteTempFile // =================================================================================================