diff options
Diffstat (limited to 'XMPFiles/source/FileHandlers/P2_Handler.cpp')
-rw-r--r-- | XMPFiles/source/FileHandlers/P2_Handler.cpp | 1401 |
1 files changed, 1401 insertions, 0 deletions
diff --git a/XMPFiles/source/FileHandlers/P2_Handler.cpp b/XMPFiles/source/FileHandlers/P2_Handler.cpp new file mode 100644 index 0000000..295a7a1 --- /dev/null +++ b/XMPFiles/source/FileHandlers/P2_Handler.cpp @@ -0,0 +1,1401 @@ +// ================================================================================================= +// 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/P2_Handler.hpp" + +#include "third-party/zuid/interfaces/MD5.h" + +#include <cmath> + +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 have 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 ) +{ + Host_IO::AutoFolder aFolder; + 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 ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFolder ) return false; + + aFolder.folder = Host_IO::OpenFolder ( tempPath.c_str() ); + int numChildrenFound = 0; + std::string childPath; + + while ( ( Host_IO::GetNextChild ( aFolder.folder, &childName ) && ( numChildrenFound < kNumRequiredContentFolders ) ) ) { // Make sure the children of CONTENTS are legit. + if ( CheckContentFolderName ( childName ) ) { + childPath = tempPath; + childPath += kDirChar; + childPath += childName; + if ( Host_IO::GetFileMode ( childPath.c_str() ) != Host_IO::kFMode_IsFolder ) return false; + ++numChildrenFound; + } + } + aFolder.Close(); + + // Make sure the clip's .XML file exists. + + InternalMakeClipFilePath ( &tempPath, rootPath, clipName, ".XML" ); + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::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->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for P2 clip path", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. + + return true; + +} // P2_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 leaf name, with the + // extension removed. Files in the AUDIO and VOICE folders have an extra 2 digits appended + // to the clip name. The movie root path ends two levels up. + + std::string clipName, parentName, ignored; + + XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name. + XIO::SplitFileExtension ( &clipName, &ignored ); + + XIO::SplitLeafName ( &pseudoPath, &parentName ); // Remove the 2 intermediate folder levels. + XIO::SplitLeafName ( &pseudoPath, &ignored ); + + if ( (parentName == "AUDIO") | (parentName == "VOICE") ) { + if ( clipName.size() >= 3 ) clipName.erase ( clipName.size() - 2 ); + } + + pseudoPath += kDirChar; + pseudoPath += clipName; + + } + + pathLen = pseudoPath.size() + 1; // Include a terminating nul. + tempPtr = malloc ( pathLen ); + if ( tempPtr == 0 ) XMP_Throw ( "No memory for P2 clip info", kXMPErr_NoMemory ); + memcpy ( tempPtr, pseudoPath.c_str(), pathLen ); + + return tempPtr; + +} // CreatePseudoClipPath + +// ================================================================================================= +// 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 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 ); + +} // P2_MetaHandler::P2_MetaHandler + +// ================================================================================================= +// P2_MetaHandler::~P2_MetaHandler +// =============================== + +P2_MetaHandler::~P2_MetaHandler() +{ + + this->CleanupLegacyXML(); + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + } + +} // P2_MetaHandler::~P2_MetaHandler + +// ================================================================================================= +// P2_MetaHandler::MakeClipFilePath +// ================================ + +bool P2_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) +{ + + InternalMakeClipFilePath ( path, this->rootPath, this->clipName, suffix ); + if ( ! checkFile ) return true; + + return Host_IO::Exists ( path->c_str() ); + +} // P2_MetaHandler::MakeClipFilePath + +// ================================================================================================= +// P2_MetaHandler::CleanupLegacyXML +// ================================ + +void P2_MetaHandler::CleanupLegacyXML() +{ + + 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.compare ( 0, 6, "AVC-I_" ) == 0 ) ) { + + // This is AVC-Intra footage. The framerate and PAR depend on the "class" attribute in the P2 XML. + const XMP_StringPtr codecClass = legacyProp->GetAttrValue( "Class" ); + + if ( XMP_LitMatch ( codecClass, "100" ) ) { + + dmVideoCompressor = "AVC-Intra 100"; + dmPixelAspectRatio = "1/1"; + + if ( p2Codec.compare ( 6, 4, "1080" ) == 0 ) { + dmHeight = "1080"; + dmWidth = "1920"; + } else if ( p2Codec.compare ( 6, 3, "720" ) == 0 ) { + dmHeight = "720"; + dmWidth = "1280"; + } + + } else if ( XMP_LitMatch ( codecClass, "50" ) ) { + + dmVideoCompressor = "AVC-Intra 50"; + dmPixelAspectRatio = "1920/1440"; + + if ( p2Codec.compare ( 6, 4, "1080" ) == 0 ) { + dmHeight = "1080"; + dmWidth = "1440"; + } else if ( p2Codec.compare ( 6, 3, "720" ) == 0 ) { + dmHeight = "720"; + dmWidth = "960"; + } + + } else { + // Unknown codec class -- we don't have enough info to determine the + // codec, PAR, or aspect ratio + dmVideoCompressor = "AVC-Intra"; + } + } + + if ( dmWidth == "720" ) { + + // 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(); + XMP_StringPtr p2DropFrameFlag = legacyProp->GetAttrValue ( "DropFrameFlag" ); + if ( p2DropFrameFlag == 0 ) p2DropFrameFlag = ""; // Make tests easier. + 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 ( XMP_LitMatch ( p2DropFrameFlag, "true" ) ) { + dmTimeFormat = "5994DropTimecode"; + } else if ( XMP_LitMatch ( p2DropFrameFlag, "false" ) ) { + dmTimeFormat = "5994NonDropTimecode"; + } + + } else if ( (p2FrameRate == "59.94i") || (p2FrameRate == "29.97p") ) { + + if ( p2DropFrameFlag != 0 ) { + + if ( XMP_LitMatch ( p2DropFrameFlag, "false" ) ) { + + dmTimeFormat = "2997NonDropTimecode"; + + } else if ( XMP_LitMatch ( p2DropFrameFlag, "true" ) ) { + + // 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::SetGPSPropertyFromLegacyXML +// =========================================== + +void P2_MetaHandler::SetGPSPropertyFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound, XMP_StringPtr propName, XMP_StringPtr legacyPropName ) +{ + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_EXIF, propName )) ) { + + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyGPSProp = legacyLocationContext->GetNamedElement ( p2NS, legacyPropName ); + + if ( ( legacyGPSProp != 0 ) && legacyGPSProp->IsLeafContentNode() ) { + + this->xmpObj.DeleteProperty ( kXMP_NS_EXIF, propName ); + + const std::string legacyGPSValue = legacyGPSProp->GetLeafContentValue(); + + if ( ! legacyGPSValue.empty() ) { + + // Convert from decimal to sexagesimal GPS coordinates + char direction = '\0'; + double degrees = 0.0; + const int numFieldsRead = sscanf ( legacyGPSValue.c_str(), "%c%lf", &direction, °rees ); + + if ( numFieldsRead == 2 ) { + double wholeDegrees = 0.0; + const double fractionalDegrees = modf ( degrees, &wholeDegrees ); + const double minutes = fractionalDegrees * 60.0; + char xmpValue [128]; + + sprintf ( xmpValue, "%d,%.5lf%c", static_cast<int>(wholeDegrees), minutes, direction ); + this->xmpObj.SetProperty ( kXMP_NS_EXIF, propName, xmpValue ); + this->containsXMP = true; + + } + + } + + } + + } + +} // P2_MetaHandler::SetGPSPropertyFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetAltitudeFromLegacyXML +// ======================================== + +void P2_MetaHandler::SetAltitudeFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound ) +{ + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_EXIF, "GPSAltitude" )) ) { + + XMP_StringPtr p2NS = this->p2NS.c_str(); + XML_NodePtr legacyAltitudeProp = legacyLocationContext->GetNamedElement ( p2NS, "Altitude" ); + + if ( ( legacyAltitudeProp != 0 ) && legacyAltitudeProp->IsLeafContentNode() ) { + + this->xmpObj.DeleteProperty ( kXMP_NS_EXIF, "GPSAltitude" ); + + const std::string legacyGPSValue = legacyAltitudeProp->GetLeafContentValue(); + + if ( ! legacyGPSValue.empty() ) { + + int altitude = 0; + + if ( sscanf ( legacyGPSValue.c_str(), "%d", &altitude ) == 1) { + + if ( altitude >= 0 ) { + // At or above sea level. + this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "0" ); + } else { + // Below sea level. + altitude = -altitude; + this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "1" ); + } + + char xmpValue [128]; + + sprintf ( xmpValue, "%d/1", altitude ); + this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitude", xmpValue ); + this->containsXMP = true; + + } + + } + + } + + } + +} // P2_MetaHandler::SetAltitudeFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::ForceChildElement +// ================================= + +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" ); + /* Rather return than create the digest because the "Access" element is listed as "required" in the P2 spec. + So a P2 file without an "Access" element does not follow the spec and might be corrupt.*/ + if ( legacyContext == 0 ) return; + + this->DigestLegacyItem ( md5Context, legacyContext, "Creator" ); + this->DigestLegacyItem ( md5Context, legacyContext, "CreationDate" ); + this->DigestLegacyItem ( md5Context, legacyContext, "LastUpdateDate" ); + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Shoot" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "Shooter" ); + + legacyContext = legacyContext->GetNamedElement ( p2NS, "Location" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "PlaceName" ); + this->DigestLegacyItem ( md5Context, legacyContext, "Longitude" ); + this->DigestLegacyItem ( md5Context, legacyContext, "Latitude" ); + this->DigestLegacyItem ( md5Context, legacyContext, "Altitude" ); + } + } + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Scenario" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "SceneNo." ); + this->DigestLegacyItem ( md5Context, legacyContext, "TakeNo." ); + } + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Device" ); + + if ( legacyContext != 0 ) { + this->DigestLegacyItem ( md5Context, legacyContext, "Manufacturer" ); + this->DigestLegacyItem ( md5Context, legacyContext, "SerialNo." ); + this->DigestLegacyItem ( md5Context, legacyContext, "ModelName" ); + } + + MD5Final ( digestBin, &md5Context ); + + char buffer [40]; + 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::GetFileModDate +// ============================== + +static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) { + int compare = SXMPUtils::CompareDateTime ( left, right ); + return (compare < 0); +} + +bool P2_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) +{ + + // The P2 locations of metadata: + // CONTENTS/ + // CLIP/ + // 0001AB.XML + // 0001AB.XMP + + bool ok, haveDate = false; + std::string fullPath; + XMP_DateTime oneDate, junkDate; + if ( modDate == 0 ) modDate = &junkDate; + + ok = this->MakeClipFilePath ( &fullPath, ".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, ".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; + +} // P2_MetaHandler::GetFileModDate + +// ================================================================================================= +// P2_MetaHandler::CacheFileData +// ============================= + +void P2_MetaHandler::CacheFileData() +{ + XMP_Assert ( ! this->containsXMP ); + + if ( this->parent->UsesClientIO() ) { + XMP_Throw ( "XDCAM cannot be used with client-managed I/O", kXMPErr_InternalFailure ); + } + + // Make sure the clip's .XMP file exists. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, ".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 ( "P2 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; + +} // 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() ); + } + + std::string xmlPath; + this->MakeClipFilePath ( &xmlPath, ".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 ( "P2_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 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_PropArrayIsOrdered, + 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_DM, "good" )) ) { + legacyProp = this->clipMetadata->GetNamedElement ( p2NS, "ShotMark" ); + if ( (legacyProp == 0) || (! legacyProp->IsLeafContentNode()) ) { + this->xmpObj.DeleteProperty ( kXMP_NS_DM, "good" ); + } else { + XMP_StringPtr markValue = legacyProp->GetLeafContentValue(); + if ( markValue == 0 ) { + this->xmpObj.DeleteProperty ( kXMP_NS_DM, "good" ); + } else if ( XMP_LitMatch ( markValue, "true" ) || XMP_LitMatch ( markValue, "1" ) ) { + this->xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", true, kXMP_DeleteExisting ); + this->containsXMP = true; + } else if ( XMP_LitMatch ( markValue, "false" ) || XMP_LitMatch ( markValue, "0" ) ) { + this->xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", false, kXMP_DeleteExisting ); + this->containsXMP = true; + } + } + } + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Shoot" ); + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Artist", "Shooter", false ); + legacyContext = legacyContext->GetNamedElement ( p2NS, "Location" ); + } + + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "shotLocation", "PlaceName", false ); + this->SetGPSPropertyFromLegacyXML ( legacyContext, digestFound, "GPSLongitude", "Longitude" ); + this->SetGPSPropertyFromLegacyXML ( legacyContext, digestFound, "GPSLatitude", "Latitude" ); + this->SetAltitudeFromLegacyXML ( legacyContext, digestFound ); + } + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Device" ); + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Make", "Manufacturer", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_EXIF_Aux, "SerialNumber", "SerialNo.", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Model", "ModelName", false ); + } + + legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Scenario" ); + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "scene", "SceneNo.", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "takeNumber", "TakeNo.", false ); + } + + CleanupAndExit + #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. + + 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 ) { + + 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 XMP file first, don't let legacy XML failures block the XMP. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, ".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 P2 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, ".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 P2 legacy XML file", kXMPErr_ExternalFailure ); + XMPFiles_IO origXML ( hostRef, xmlPath.c_str(), Host_IO::openReadWrite ); + XIO::ReplaceTextFile ( &origXML, legacyXML, (haveXML & doSafeUpdate) ); + origXML.Close(); + + } + +} // P2_MetaHandler::UpdateFile + +// ================================================================================================= +// P2_MetaHandler::WriteTempFile +// ============================= + +void P2_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + + // ! WriteTempFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "P2_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure ); + +} // P2_MetaHandler::WriteTempFile + +// ================================================================================================= |