summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/XMPCore/ExpatAdapter.cpp117
-rw-r--r--source/XMPCore/ParseRDF.cpp24
-rw-r--r--source/XMPCore/WXMPMeta.cpp37
-rw-r--r--source/XMPCore/WXMPUtils.cpp2
-rw-r--r--source/XMPCore/XMLParserAdapter.hpp53
-rw-r--r--source/XMPCore/XMPCore_Impl.cpp50
-rw-r--r--source/XMPCore/XMPCore_Impl.hpp134
-rw-r--r--source/XMPCore/XMPMeta-GetSet.cpp7
-rw-r--r--source/XMPCore/XMPMeta-Parse.cpp57
-rw-r--r--source/XMPCore/XMPMeta-Serialize.cpp23
-rw-r--r--source/XMPCore/XMPMeta.cpp303
-rw-r--r--source/XMPCore/XMPMeta.hpp12
-rw-r--r--source/XMPCore/XMPUtils-FileInfo.cpp171
-rw-r--r--source/XMPCore/XMPUtils.cpp45
-rw-r--r--source/XMPCore/XMPUtils.hpp1
-rw-r--r--source/XMPFiles/FileHandlers/ASF_Handler.cpp362
-rw-r--r--source/XMPFiles/FileHandlers/ASF_Handler.hpp66
-rw-r--r--source/XMPFiles/FileHandlers/AVCHD_Handler.cpp648
-rw-r--r--source/XMPFiles/FileHandlers/AVCHD_Handler.hpp77
-rw-r--r--source/XMPFiles/FileHandlers/AVI_Handler.cpp148
-rw-r--r--source/XMPFiles/FileHandlers/Basic_Handler.cpp4
-rw-r--r--source/XMPFiles/FileHandlers/Basic_Handler.hpp1
-rw-r--r--source/XMPFiles/FileHandlers/FLV_Handler.cpp750
-rw-r--r--source/XMPFiles/FileHandlers/FLV_Handler.hpp73
-rw-r--r--source/XMPFiles/FileHandlers/InDesign_Handler.cpp22
-rw-r--r--source/XMPFiles/FileHandlers/JPEG_Handler.cpp59
-rw-r--r--source/XMPFiles/FileHandlers/MOV_Handler.cpp696
-rw-r--r--source/XMPFiles/FileHandlers/MOV_Handler.hpp20
-rw-r--r--source/XMPFiles/FileHandlers/MP3_Handler.cpp27
-rw-r--r--source/XMPFiles/FileHandlers/MP3_Handler.hpp4
-rw-r--r--source/XMPFiles/FileHandlers/MPEG2_Handler.cpp (renamed from source/XMPFiles/FileHandlers/MPEG_Handler.cpp)88
-rw-r--r--source/XMPFiles/FileHandlers/MPEG2_Handler.hpp (renamed from source/XMPFiles/FileHandlers/MPEG_Handler.hpp)38
-rw-r--r--source/XMPFiles/FileHandlers/MPEG4_Handler.cpp909
-rw-r--r--source/XMPFiles/FileHandlers/MPEG4_Handler.hpp69
-rw-r--r--source/XMPFiles/FileHandlers/P2_Handler.cpp1203
-rw-r--r--source/XMPFiles/FileHandlers/P2_Handler.hpp106
-rw-r--r--source/XMPFiles/FileHandlers/PNG_Handler.cpp6
-rw-r--r--source/XMPFiles/FileHandlers/PSD_Handler.cpp29
-rw-r--r--source/XMPFiles/FileHandlers/PostScript_Handler.cpp8
-rw-r--r--source/XMPFiles/FileHandlers/SWF_Handler.cpp397
-rw-r--r--source/XMPFiles/FileHandlers/SWF_Handler.hpp65
-rw-r--r--source/XMPFiles/FileHandlers/Scanner_Handler.cpp8
-rw-r--r--source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp782
-rw-r--r--source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp77
-rw-r--r--source/XMPFiles/FileHandlers/TIFF_Handler.cpp45
-rw-r--r--source/XMPFiles/FileHandlers/UCF_Handler.cpp846
-rw-r--r--source/XMPFiles/FileHandlers/UCF_Handler.hpp716
-rw-r--r--source/XMPFiles/FileHandlers/WAV_Handler.cpp109
-rw-r--r--source/XMPFiles/FileHandlers/WAV_Handler.hpp8
-rw-r--r--source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp824
-rw-r--r--source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp81
-rw-r--r--source/XMPFiles/FileHandlers/XDCAM_Handler.cpp726
-rw-r--r--source/XMPFiles/FileHandlers/XDCAM_Handler.hpp82
-rw-r--r--source/XMPFiles/FormatSupport/ASF_Support.cpp1434
-rw-r--r--source/XMPFiles/FormatSupport/ASF_Support.hpp224
-rw-r--r--source/XMPFiles/FormatSupport/ID3_Support.cpp37
-rw-r--r--source/XMPFiles/FormatSupport/ID3_Support.hpp2
-rw-r--r--source/XMPFiles/FormatSupport/IPTC_Support.cpp28
-rw-r--r--source/XMPFiles/FormatSupport/IPTC_Support.hpp4
-rw-r--r--source/XMPFiles/FormatSupport/PNG_Support.cpp4
-rw-r--r--source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp183
-rw-r--r--source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp4
-rw-r--r--source/XMPFiles/FormatSupport/PSIR_Support.hpp70
-rw-r--r--source/XMPFiles/FormatSupport/QuickTime_Support.cpp20
-rw-r--r--source/XMPFiles/FormatSupport/QuickTime_Support.hpp8
-rw-r--r--source/XMPFiles/FormatSupport/RIFF_Support.cpp474
-rw-r--r--source/XMPFiles/FormatSupport/RIFF_Support.hpp23
-rw-r--r--source/XMPFiles/FormatSupport/ReconcileIPTC.cpp53
-rw-r--r--source/XMPFiles/FormatSupport/ReconcileLegacy.cpp23
-rw-r--r--source/XMPFiles/FormatSupport/ReconcileTIFF.cpp177
-rw-r--r--source/XMPFiles/FormatSupport/Reconcile_Impl.cpp50
-rw-r--r--source/XMPFiles/FormatSupport/Reconcile_Impl.hpp13
-rw-r--r--source/XMPFiles/FormatSupport/SWF_Support.cpp844
-rw-r--r--source/XMPFiles/FormatSupport/SWF_Support.hpp254
-rw-r--r--source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp356
-rw-r--r--source/XMPFiles/FormatSupport/TIFF_MemoryReader.cpp65
-rw-r--r--source/XMPFiles/FormatSupport/TIFF_Support.cpp2
-rw-r--r--source/XMPFiles/FormatSupport/TIFF_Support.hpp98
-rw-r--r--source/XMPFiles/FormatSupport/XDCAM_Support.cpp290
-rw-r--r--source/XMPFiles/FormatSupport/XDCAM_Support.hpp43
-rw-r--r--source/XMPFiles/FormatSupport/XMPScanner.cpp26
-rw-r--r--source/XMPFiles/WXMPFiles.cpp24
-rw-r--r--source/XMPFiles/XMPFiles.cpp962
-rw-r--r--source/XMPFiles/XMPFiles.hpp3
-rw-r--r--source/XMPFiles/XMPFiles_Impl.cpp1133
-rw-r--r--source/XMPFiles/XMPFiles_Impl.hpp194
-rw-r--r--source/common/EndianUtils.hpp (renamed from source/XMPFiles/FormatSupport/EndianUtils.hpp)25
-rw-r--r--source/common/ExpatAdapter.hpp (renamed from source/XMPCore/ExpatAdapter.hpp)26
-rw-r--r--source/common/LargeFileAccess.cpp814
-rw-r--r--source/common/LargeFileAccess.hpp139
-rw-r--r--source/common/XMLParserAdapter.hpp140
-rw-r--r--source/common/XML_Node.cpp459
92 files changed, 17737 insertions, 2206 deletions
diff --git a/source/XMPCore/ExpatAdapter.cpp b/source/XMPCore/ExpatAdapter.cpp
index d383c98..3dff828 100644
--- a/source/XMPCore/ExpatAdapter.cpp
+++ b/source/XMPCore/ExpatAdapter.cpp
@@ -1,5 +1,5 @@
// =================================================================================================
-// Copyright 2005-2007 Adobe Systems Incorporated
+// Copyright 2005-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -12,6 +12,8 @@
#include "ExpatAdapter.hpp"
#include "XMPMeta.hpp"
+#include "expat.h"
+
#include <string.h>
using namespace std;
@@ -23,7 +25,7 @@ using namespace std;
// *** Set memory handlers.
#ifndef DumpXMLParseEvents
- #define DumpXMLParseEvents 0
+ #define DumpXMLParseEvents 0
#endif
#define FullNameSeparator '@'
@@ -43,16 +45,38 @@ static void EndCdataSectionHandler ( void * userData );
static void ProcessingInstructionHandler ( void * userData, XMP_StringPtr target, XMP_StringPtr data );
static void CommentHandler ( void * userData, XMP_StringPtr comment );
-static void DefaultHandler ( void * userData, XMP_StringPtr data, int len );
+#if BanAllEntityUsage
+
+ // For now we do this by banning DOCTYPE entirely. This is easy and consistent with what is
+ // available in recent Java XML parsers. Another, somewhat less drastic, approach would be to
+ // ban all entity declarations. We can't allow declarations and ban references, Expat does not
+ // call the SkippedEntityHandler for references in attribute values.
+
+ // ! Standard entities (&amp;, &lt;, &gt;, &quot;, &apos;, and numeric character references) are
+ // ! not banned. Expat handles them transparently no matter what.
+
+ static void StartDoctypeDeclHandler ( void * userData, XMP_StringPtr doctypeName,
+ XMP_StringPtr sysid, XMP_StringPtr pubid, int has_internal_subset );
+
+#endif
// =================================================================================================
+
+extern "C" ExpatAdapter * XMP_NewExpatAdapter()
+{
+ return new ExpatAdapter;
+} // XMP_NewExpatAdapter
+
// =================================================================================================
-ExpatAdapter::ExpatAdapter() : parser(0), nesting(0)
+ExpatAdapter::ExpatAdapter() : parser(0)
{
- #if XMP_DebugBuild & DumpXMLParseEvents
- if ( this->parseLog == 0 ) this->parseLog = stdout;
+ #if XMP_DebugBuild
+ this->elemNesting = 0;
+ #if DumpXMLParseEvents
+ if ( this->parseLog == 0 ) this->parseLog = stdout;
+ #endif
#endif
this->parser = XML_ParserCreateNS ( 0, FullNameSeparator );
@@ -69,8 +93,11 @@ ExpatAdapter::ExpatAdapter() : parser(0), nesting(0)
XML_SetProcessingInstructionHandler ( this->parser, ProcessingInstructionHandler );
XML_SetCommentHandler ( this->parser, CommentHandler );
- // ??? XML_SetDefaultHandlerExpand ( this->parser, DefaultHandler );
-
+ #if BanAllEntityUsage
+ XML_SetStartDoctypeDeclHandler ( this->parser, StartDoctypeDeclHandler );
+ isAborted = false;
+ #endif
+
this->parseStack.push_back ( &this->tree ); // Push the XML root node.
} // ExpatAdapter::ExpatAdapter
@@ -93,7 +120,7 @@ ExpatAdapter::~ExpatAdapter()
static const char * kOneSpace = " ";
-void ExpatAdapter::ParseBuffer ( const void * buffer, size_t length, bool last )
+void ExpatAdapter::ParseBuffer ( const void * buffer, size_t length, bool last /* = true */ )
{
enum XML_Status status;
@@ -104,6 +131,10 @@ void ExpatAdapter::ParseBuffer ( const void * buffer, size_t length, bool last )
}
status = XML_Parse ( this->parser, (const char *)buffer, length, last );
+
+ #if BanAllEntityUsage
+ if ( this->isAborted ) XMP_Throw ( "DOCTYPE is not allowed", kXMPErr_BadXML );
+ #endif
if ( status != XML_STATUS_OK ) {
@@ -141,9 +172,9 @@ void ExpatAdapter::ParseBuffer ( const void * buffer, size_t length, bool last )
#if XMP_DebugBuild & DumpXMLParseEvents
- static inline void PrintIndent ( FILE * file, size_t nesting )
+ static inline void PrintIndent ( FILE * file, size_t count )
{
- for ( ; nesting > 0; --nesting ) fprintf ( file, " " );
+ for ( ; count > 0; --count ) fprintf ( file, " " );
}
#endif
@@ -170,12 +201,15 @@ static void SetQualName ( XMP_StringPtr fullName, XML_Node * node )
if ( fullName[sepPos] == FullNameSeparator ) {
XMP_StringPtr prefix;
+ XMP_StringLen prefixLen;
XMP_StringPtr localPart = fullName + sepPos + 1;
node->ns.assign ( fullName, sepPos );
if ( node->ns == "http://purl.org/dc/1.1/" ) node->ns = "http://purl.org/dc/elements/1.1/";
- bool found = XMPMeta::GetNamespacePrefix ( node->ns.c_str(), &prefix, &voidStringLen );
+
+ bool found = XMPMeta::GetNamespacePrefix ( node->ns.c_str(), &prefix, &prefixLen );
if ( ! found ) XMP_Throw ( "Unknown URI in Expat full name", kXMPErr_ExternalFailure );
+ node->nsPrefixLen = prefixLen; // ! Includes the ':'.
node->name = prefix;
node->name += localPart;
@@ -188,9 +222,11 @@ static void SetQualName ( XMP_StringPtr fullName, XML_Node * node )
if ( node->name == "about" ) {
node->ns = kXMP_NS_RDF;
node->name = "rdf:about";
+ node->nsPrefixLen = 4; // ! Include the ':'.
} else if ( node->name == "ID" ) {
node->ns = kXMP_NS_RDF;
node->name = "rdf:ID";
+ node->nsPrefixLen = 4; // ! Include the ':'.
}
}
@@ -216,7 +252,7 @@ static void StartNamespaceDeclHandler ( void * userData, XMP_StringPtr prefix, X
#if XMP_DebugBuild & DumpXMLParseEvents
if ( thiz->parseLog != 0 ) {
- PrintIndent ( thiz->parseLog, thiz->nesting );
+ PrintIndent ( thiz->parseLog, thiz->elemNesting );
fprintf ( thiz->parseLog, "StartNamespace: %s - \"%s\"\n", prefix, uri );
}
#endif
@@ -240,7 +276,7 @@ static void EndNamespaceDeclHandler ( void * userData, XMP_StringPtr prefix )
#if XMP_DebugBuild & DumpXMLParseEvents
if ( thiz->parseLog != 0 ) {
- PrintIndent ( thiz->parseLog, thiz->nesting );
+ PrintIndent ( thiz->parseLog, thiz->elemNesting );
fprintf ( thiz->parseLog, "EndNamespace: %s\n", prefix );
}
#endif
@@ -263,7 +299,7 @@ static void StartElementHandler ( void * userData, XMP_StringPtr name, XMP_Strin
#if XMP_DebugBuild & DumpXMLParseEvents
if ( thiz->parseLog != 0 ) {
- PrintIndent ( thiz->parseLog, thiz->nesting );
+ PrintIndent ( thiz->parseLog, thiz->elemNesting );
fprintf ( thiz->parseLog, "StartElement: %s, %d attrs", name, attrCount );
for ( XMP_StringPtr* attr = attrs; *attr != 0; attr += 2 ) {
XMP_StringPtr attrName = *attr;
@@ -295,12 +331,13 @@ static void StartElementHandler ( void * userData, XMP_StringPtr name, XMP_Strin
parentNode->content.push_back ( elemNode );
thiz->parseStack.push_back ( elemNode );
- if ( (elemNode->name == "rdf:RDF") || (elemNode->name == "pxmp:XMP_Packet") ) {
+ if ( elemNode->name == "rdf:RDF" ) {
thiz->rootNode = elemNode;
++thiz->rootCount;
}
-
- ++thiz->nesting;
+ #if XMP_DebugBuild
+ ++thiz->elemNesting;
+ #endif
} // StartElementHandler
@@ -312,12 +349,14 @@ static void EndElementHandler ( void * userData, XMP_StringPtr name )
ExpatAdapter * thiz = (ExpatAdapter*)userData;
- --thiz->nesting;
+ #if XMP_DebugBuild
+ --thiz->elemNesting;
+ #endif
(void) thiz->parseStack.pop_back();
#if XMP_DebugBuild & DumpXMLParseEvents
if ( thiz->parseLog != 0 ) {
- PrintIndent ( thiz->parseLog, thiz->nesting );
+ PrintIndent ( thiz->parseLog, thiz->elemNesting );
fprintf ( thiz->parseLog, "EndElement: %s\n", name );
}
#endif
@@ -334,9 +373,9 @@ static void CharacterDataHandler ( void * userData, XMP_StringPtr cData, int len
#if XMP_DebugBuild & DumpXMLParseEvents
if ( thiz->parseLog != 0 ) {
- PrintIndent ( thiz->parseLog, thiz->nesting );
+ PrintIndent ( thiz->parseLog, thiz->elemNesting );
fprintf ( thiz->parseLog, "CharContent: \"" );
- for ( ; len > 0; --len, ++cData ) fprintf ( thiz->parseLog, "%c", *cData );
+ for ( int i = 0; i < len; ++i ) fprintf ( thiz->parseLog, "%c", cData[i] );
fprintf ( thiz->parseLog, "\"\n" );
}
#endif
@@ -361,7 +400,7 @@ static void StartCdataSectionHandler ( void * userData )
#if XMP_DebugBuild & DumpXMLParseEvents
if ( thiz->parseLog != 0 ) {
- PrintIndent ( thiz->parseLog, thiz->nesting );
+ PrintIndent ( thiz->parseLog, thiz->elemNesting );
fprintf ( thiz->parseLog, "StartCDATA\n" );
}
#endif
@@ -382,7 +421,7 @@ static void EndCdataSectionHandler ( void * userData )
#if XMP_DebugBuild & DumpXMLParseEvents
if ( thiz->parseLog != 0 ) {
- PrintIndent ( thiz->parseLog, thiz->nesting );
+ PrintIndent ( thiz->parseLog, thiz->elemNesting );
fprintf ( thiz->parseLog, "EndCDATA\n" );
}
#endif
@@ -401,7 +440,7 @@ static void ProcessingInstructionHandler ( void * userData, XMP_StringPtr target
#if XMP_DebugBuild & DumpXMLParseEvents
if ( thiz->parseLog != 0 ) {
- PrintIndent ( thiz->parseLog, thiz->nesting );
+ PrintIndent ( thiz->parseLog, thiz->elemNesting );
fprintf ( thiz->parseLog, "PI: %s - \"%s\"\n", target, data );
}
#endif
@@ -428,7 +467,7 @@ static void CommentHandler ( void * userData, XMP_StringPtr comment )
#if XMP_DebugBuild & DumpXMLParseEvents
if ( thiz->parseLog != 0 ) {
- PrintIndent ( thiz->parseLog, thiz->nesting );
+ PrintIndent ( thiz->parseLog, thiz->elemNesting );
fprintf ( thiz->parseLog, "Comment: \"%s\"\n", comment );
}
#endif
@@ -439,27 +478,25 @@ static void CommentHandler ( void * userData, XMP_StringPtr comment )
// =================================================================================================
-static void DefaultHandler ( void * userData, XMP_StringPtr data, int len )
+#if BanAllEntityUsage
+static void StartDoctypeDeclHandler ( void * userData, XMP_StringPtr doctypeName,
+ XMP_StringPtr sysid, XMP_StringPtr pubid, int has_internal_subset )
{
IgnoreParam(userData);
- #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning.
- ExpatAdapter * thiz = (ExpatAdapter*)userData;
- #endif
+ ExpatAdapter * thiz = (ExpatAdapter*)userData;
- if ( (data == 0) || (len == 0) ) { data = ""; len = 0; }
-
- #if XMP_DebugBuild & DumpXMLParseEvents
+ #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning.
if ( thiz->parseLog != 0 ) {
- PrintIndent ( thiz->parseLog, thiz->nesting );
- fprintf ( thiz->parseLog, ">>default<<: \"" );
- for ( ; len > 0; --len, ++data ) fprintf ( thiz->parseLog, "%c", *data );
- fprintf ( thiz->parseLog, "\"\n" );
+ PrintIndent ( thiz->parseLog, thiz->elemNesting );
+ fprintf ( thiz->parseLog, "DocType: \"%s\"\n", doctypeName );
}
#endif
- // *** Ignore for now. Complain if not whitespace?
-
-} // DefaultHandler
+ thiz->isAborted = true; // ! Can't throw an exception across the plain C Expat frames.
+ (void) XML_StopParser ( thiz->parser, XML_FALSE /* not resumable */ );
+
+} // StartDoctypeDeclHandler
+#endif
// =================================================================================================
diff --git a/source/XMPCore/ParseRDF.cpp b/source/XMPCore/ParseRDF.cpp
index 575b617..31fb2b9 100644
--- a/source/XMPCore/ParseRDF.cpp
+++ b/source/XMPCore/ParseRDF.cpp
@@ -1,5 +1,5 @@
// =================================================================================================
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -8,6 +8,7 @@
#include "XMP_Environment.h" // ! This must be the first include!
#include "XMPCore_Impl.hpp"
+#include "ExpatAdapter.hpp"
#include <cstring>
@@ -511,8 +512,8 @@ AddQualifierNode ( XMP_Node * xmpParent, const XMP_VarString & name, const XMP_V
}
xmpParent->options |= kXMP_PropHasType;
}
-
- xmpParent->options |= kXMP_PropHasQualifiers; // ! Don't set for the pxmp:compact.
+
+ xmpParent->options |= kXMP_PropHasQualifiers;
return newQual;
@@ -712,7 +713,7 @@ RDF_NodeElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isT
XML_cNodePos endChild = xmlParent.content.end();
for ( ; currChild != endChild; ++currChild ) {
- if ( IsWhitespaceNode ( **currChild ) ) continue;
+ if ( (*currChild)->IsWhitespaceNode() ) continue;
RDF_NodeElement ( xmpParent, **currChild, isTopLevel );
}
@@ -796,13 +797,10 @@ RDF_NodeElementAttrs ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTo
// This is the rdf:about attribute on a top level node. Set the XMP tree name if
// it doesn't have a name yet. Make sure this name matches the XMP tree name.
XMP_Assert ( xmpParent->parent == 0 ); // Must be the tree root node.
- if ( ! xmpParent->name.empty() ) {
- if ( xmpParent->name != (*currAttr)->value ) XMP_Throw ( "Mismatched top level rdf:about values", kXMPErr_BadXMP );
- } else {
+ if ( xmpParent->name.empty() ) {
xmpParent->name = (*currAttr)->value;
- #if 0 // *** XMP_DebugBuild
- xmpParent->_namePtr = xmpParent->name.c_str();
- #endif
+ } else if ( ! (*currAttr)->value.empty() ) {
+ if ( xmpParent->name != (*currAttr)->value ) XMP_Throw ( "Mismatched top level rdf:about values", kXMPErr_BadXMP );
}
}
@@ -836,7 +834,7 @@ RDF_PropertyElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool
XML_cNodePos endChild = xmlParent.content.end();
for ( ; currChild != endChild; ++currChild ) {
- if ( IsWhitespaceNode ( **currChild ) ) continue;
+ if ( (*currChild)->IsWhitespaceNode() ) continue;
if ( (*currChild)->kind != kElemNode ) {
XMP_Throw ( "Expected property element node not found", kXMPErr_BadRDF );
}
@@ -1010,7 +1008,7 @@ RDF_ResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bo
XML_cNodePos endChild = xmlNode.content.end();
for ( ; currChild != endChild; ++currChild ) {
- if ( ! IsWhitespaceNode ( **currChild ) ) break;
+ if ( ! (*currChild)->IsWhitespaceNode() ) break;
}
if ( currChild == endChild ) XMP_Throw ( "Missing child of resource property element", kXMPErr_BadRDF );
if ( (*currChild)->kind != kElemNode ) XMP_Throw ( "Children of resource property element must be XML elements", kXMPErr_BadRDF );
@@ -1040,7 +1038,7 @@ RDF_ResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bo
}
for ( ++currChild; currChild != endChild; ++currChild ) {
- if ( ! IsWhitespaceNode ( **currChild ) ) XMP_Throw ( "Invalid child of resource property element", kXMPErr_BadRDF );
+ if ( ! (*currChild)->IsWhitespaceNode() ) XMP_Throw ( "Invalid child of resource property element", kXMPErr_BadRDF );
}
} // RDF_ResourcePropertyElement
diff --git a/source/XMPCore/WXMPMeta.cpp b/source/XMPCore/WXMPMeta.cpp
index 937409a..4c95c5d 100644
--- a/source/XMPCore/WXMPMeta.cpp
+++ b/source/XMPCore/WXMPMeta.cpp
@@ -1,5 +1,5 @@
// =================================================================================================
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -127,7 +127,7 @@ WXMPMeta_DecrementRefCount_1 ( XMPMetaRef xmpRef )
// validate parameters
// call through to the implementation
// retain toolbox lock if necessary
-// catch anything and return an appropriate BIBError object
+// catch anything and return an appropriate XMP_Error object
// return null (no error if we get to here)
//
// The toolbox lock is acquired through a local wrapper object that automatically unlocks when the
@@ -385,7 +385,7 @@ WXMPMeta_RegisterStandardAliases_1 ( XMP_StringPtr schemaNS,
// acquire object lock
// call through to the implementation
// retain object lock if necessary
-// catch anything and return an appropriate BIBError object
+// catch anything and return an appropriate XMP_Error object
// return null (no error if we get to here)
//
// The object lock is acquired through a local wrapper object that automatically unlocks when the
@@ -1119,6 +1119,34 @@ WXMPMeta_DumpObject_1 ( XMPMetaRef xmpRef,
// -------------------------------------------------------------------------------------------------
void
+WXMPMeta_Sort_1 ( XMPMetaRef xmpRef,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_WRAPPER ( "WXMPMeta_Sort_1" )
+
+ XMPMeta * meta = WtoXMPMeta_Ptr ( xmpRef );
+ meta->Sort();
+
+ XMP_EXIT_WRAPPER
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void
+WXMPMeta_Erase_1 ( XMPMetaRef xmpRef,
+ WXMP_Result * wResult )
+{
+ XMP_ENTER_WRAPPER ( "WXMPMeta_Erase_1" )
+
+ XMPMeta * meta = WtoXMPMeta_Ptr ( xmpRef );
+ meta->Erase();
+
+ XMP_EXIT_WRAPPER
+}
+
+// -------------------------------------------------------------------------------------------------
+
+void
WXMPMeta_Clone_1 ( XMPMetaRef xmpRef,
XMP_OptionBits options,
WXMP_Result * wResult ) /* const */
@@ -1126,7 +1154,8 @@ WXMPMeta_Clone_1 ( XMPMetaRef xmpRef,
XMP_ENTER_WRAPPER ( "WXMPMeta_Clone_1" )
const XMPMeta & xOriginal = WtoXMPMeta_Ref ( xmpRef );
- XMPMeta * xClone = xOriginal.Clone ( options );
+ XMPMeta * xClone = new XMPMeta;
+ xOriginal.Clone ( xClone, options );
XMP_Assert ( xClone->clientRefs == 0 ); // ! Gets incremented in TXMPMeta::Clone.
wResult->ptrResult = xClone;
diff --git a/source/XMPCore/WXMPUtils.cpp b/source/XMPCore/WXMPUtils.cpp
index 1ab33f3..dac093a 100644
--- a/source/XMPCore/WXMPUtils.cpp
+++ b/source/XMPCore/WXMPUtils.cpp
@@ -1,5 +1,5 @@
// =================================================================================================
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
diff --git a/source/XMPCore/XMLParserAdapter.hpp b/source/XMPCore/XMLParserAdapter.hpp
deleted file mode 100644
index 2d78779..0000000
--- a/source/XMPCore/XMLParserAdapter.hpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#ifndef __XMLParserAdapter_hpp__
-#define __XMLParserAdapter_hpp__
-
-// =================================================================================================
-// Copyright 2005-2007 Adobe Systems Incorporated
-// All Rights Reserved.
-//
-// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
-// of the Adobe license agreement accompanying it.
-// =================================================================================================
-
-#include "XMP_Environment.h" // ! Must be the first #include!
-#include "XMPCore_Impl.hpp"
-
-// =================================================================================================
-// Abstract base class for XML parser adapters used by the XMP toolkit.
-// =================================================================================================
-
-enum { kXMLPendingInputMax = 16 };
-
-class XMLParserAdapter {
-public:
-
- XMLParserAdapter()
- : tree(0,"",kRootNode), rootNode(0), rootCount(0), charEncoding(XMP_OptionBits(-1)), pendingCount(0)
- {
- #if XMP_DebugBuild
- parseLog = 0;
- #endif
- };
-
- virtual ~XMLParserAdapter() {};
-
- virtual void ParseBuffer ( const void * buffer, size_t length, bool last ) = 0;
-
- XML_Node tree;
- XML_NodeVector parseStack;
- XML_Node * rootNode;
- size_t rootCount;
-
- XMP_OptionBits charEncoding;
- size_t pendingCount;
- unsigned char pendingInput[kXMLPendingInputMax]; // Buffered input for character encoding checks.
-
- #if XMP_DebugBuild
- FILE * parseLog;
- #endif
-
-};
-
-// =================================================================================================
-
-#endif // __XMLParserAdapter_hpp__
diff --git a/source/XMPCore/XMPCore_Impl.cpp b/source/XMPCore/XMPCore_Impl.cpp
index 32687ee..1cf92c6 100644
--- a/source/XMPCore/XMPCore_Impl.cpp
+++ b/source/XMPCore/XMPCore_Impl.cpp
@@ -84,28 +84,7 @@ WXMP_Result void_wResult;
// ! times. There is a single XMP lock which is acquired in the wrapper classes. Internal calls
// ! never go back out to the wrappers.
-#if XMP_MacBuild
-
- bool XMP_InitMutex ( XMP_Mutex * mutex ) {
- OSStatus err = MPCreateCriticalRegion ( mutex );
- return (err == noErr );
- }
-
- void XMP_TermMutex ( XMP_Mutex & mutex ) {
- (void) MPDeleteCriticalRegion ( mutex );
- }
-
- void XMP_EnterCriticalRegion ( XMP_Mutex & mutex ) {
- OSStatus err = MPEnterCriticalRegion ( mutex, kDurationForever );
- if ( err != noErr ) XMP_Throw ( "XMP_EnterCriticalRegion - MPEnterCriticalRegion failure", kXMPErr_ExternalFailure );
- }
-
- void XMP_ExitCriticalRegion ( XMP_Mutex & mutex ) {
- OSStatus err = MPExitCriticalRegion ( mutex );
- if ( err != noErr ) XMP_Throw ( "XMP_ExitCriticalRegion - MPExitCriticalRegion failure", kXMPErr_ExternalFailure );
- }
-
-#elif XMP_WinBuild
+#if XMP_WinBuild
bool XMP_InitMutex ( XMP_Mutex * mutex ) {
InitializeCriticalSection ( mutex );
@@ -124,8 +103,9 @@ WXMP_Result void_wResult;
LeaveCriticalSection ( &mutex );
}
-#elif XMP_UNIXBuild
+#else
+ // Use pthread for both Mac and generic UNIX.
// ! Would be nice to specify PTHREAD_MUTEX_ERRORCHECK, but the POSIX documentation is useless.
// ! Would be OK but overkill to specify PTHREAD_MUTEX_RECURSIVE.
@@ -1464,30 +1444,6 @@ DetectAltText ( XMP_Node * xmpParent )
} // DetectAltText
// =================================================================================================
-// IsWhitespaceNode
-// ================
-//
-// Return true if this is a character data node that contains only XML whitespace.
-
-// ! For now only recognize ASCII space, tab, CR, and LF.
-
-bool
-IsWhitespaceNode ( const XML_Node & xmlNode )
-{
- if ( xmlNode.kind != kCDataNode ) return false;
-
- for ( size_t i = 0; i < xmlNode.value.size(); ++i ) {
- unsigned char ch = xmlNode.value[i];
- if ( IsWhitespaceChar ( ch ) ) continue;
- // *** Add checks for other whitespace characters.
- return false; // All the checks failed, this isn't whitespace.
- }
-
- return true;
-
-} // IsWhitespaceNode
-
-// =================================================================================================
// SortNamedNodes
// ==============
//
diff --git a/source/XMPCore/XMPCore_Impl.hpp b/source/XMPCore/XMPCore_Impl.hpp
index 5afc577..d1be1c3 100644
--- a/source/XMPCore/XMPCore_Impl.hpp
+++ b/source/XMPCore/XMPCore_Impl.hpp
@@ -2,7 +2,7 @@
#define __XMPCore_Impl_hpp__
// =================================================================================================
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -13,10 +13,6 @@
#include "XMP_Const.h"
#include "XMP_BuildInfo.h"
-#ifndef UsePublicExpat
- #define UsePublicExpat 1
-#endif
-
#include "client-glue/WXMPMeta.hpp"
#include <vector>
@@ -25,14 +21,18 @@
#include <cassert>
-#if XMP_MacBuild
- #include <Multiprocessing.h>
-#elif XMP_WinBuild
+#if XMP_WinBuild
#include <Windows.h>
-#elif XMP_UNIXBuild
+#else
+ // Use pthread for both Mac and generic UNIX.
#include <pthread.h>
#endif
+#if XMP_WinBuild
+ #pragma warning ( disable : 4244 ) // possible loss of data (temporary for 64 bit builds)
+ #pragma warning ( disable : 4267 ) // possible loss of data (temporary for 64 bit builds)
+#endif
+
// =================================================================================================
// Primary internal types
@@ -40,6 +40,8 @@ class XMP_Node;
class XML_Node;
class XPathStepInfo;
+typedef XMP_Node * XMP_NodePtr;
+
typedef std::vector<XMP_Node*> XMP_NodeOffspring;
typedef XMP_NodeOffspring::iterator XMP_NodePtrPos;
@@ -106,6 +108,9 @@ extern WXMP_Result void_wResult;
#define WtoXMPIterator_Ref(iterRef) *((const XMPIterator *)(iterRef))
#define WtoXMPIterator_Ptr(iterRef) (((iterRef) == 0) ? 0 : (XMPIterator *)(iterRef))
+#define WtoXMPDocOps_Ref(docRef) *((const XMPDocOps *)(docRef))
+#define WtoXMPDocOps_Ptr(docRef) (((docRef) == 0) ? 0 : (XMPDocOps *)(docRef))
+
#define IgnoreParam(p) voidVoidPtr = (void*)&p
// =================================================================================================
@@ -192,11 +197,10 @@ extern WXMP_Result void_wResult;
// -------------------------------------------------------------------------------------------------
-#if XMP_MacBuild
- typedef MPCriticalRegionID XMP_Mutex;
-#elif XMP_WinBuild
+#if XMP_WinBuild
typedef CRITICAL_SECTION XMP_Mutex;
-#elif XMP_UNIXBuild
+#else
+ // Use pthread for both Mac and generic UNIX.
typedef pthread_mutex_t XMP_Mutex;
#endif
@@ -357,11 +361,6 @@ NormalizeLangArray ( XMP_Node * array );
extern void
DetectAltText ( XMP_Node * xmpParent );
-#define IsWhitespaceChar(ch) ( ((ch) == ' ') || ((ch) == 0x09) || ((ch) == 0x0A) || ((ch) == 0x0D) )
-
-extern bool
-IsWhitespaceNode ( const XML_Node & xmlNode );
-
extern void
SortNamedNodes ( XMP_NodeOffspring & nodeVector );
@@ -525,105 +524,6 @@ public:
: nodePtr ( new XMP_Node ( _parent, _name, _value, _options ) ) {};
};
-// =================================================================================================
-// XML_Node details
-
-// The XML_Nodes are used only during the XML/RDF parsing process. This presently uses an XML parser
-// to create an XML tree, then a recursive descent RDF recognizer to build the corresponding XMP.
-// This makes it easier to swap XML parsers and provides a clean separation of XML and RDF issues.
-// The overall parsing would be faster and use less memory if the RDF recognition were done on the
-// fly using a state machine. But it was much easier to write the recursive descent version. The
-// current implementation is pretty fast in absolute terms, so being faster might not be crucial.
-
-// Like the XMP tree, the XML tree contains vectors of pointers for down links, and offspring have
-// a pointer to their parent. Unlike the XMP tree, this is an exact XML document tree. There are no
-// introduced top level namespace nodes or rearrangement of the nodes..
-
-// The exact state of namespaces can vary during the XML parsing, depending on the parser in use.
-// By the time the RDF recognition is done though, the namespaces must be normalized. All of the
-// used namespaces must be registered, this is done automatically if necessary. All of the "live"
-// namespace prefixes will be unique. The ns field of an XML_Node is the namespace URI, the name
-// field contains a qualified name (prefix:local). This includes default namespace mapping, the
-// URI and prefix will be missing only for elements and attributes in no namespace.
-
-class XML_Node;
-enum { kRootNode = 0, kElemNode = 1, kAttrNode = 2, kCDataNode = 3, kPINode = 4 };
-
-typedef std::vector<XML_Node*> XML_NodeVector;
-typedef XML_NodeVector::iterator XML_NodePos;
-typedef XML_NodeVector::const_iterator XML_cNodePos;
-
-#if 0 // Pattern for iterating over the children or attributes:
- for ( size_t xxNum = 0, xxLim = _node_->_offspring_.size(); xxNum < xxLim; ++xxNum ) {
- const XML_Node * _curr_ = _node_->_offspring_[xxNum];
- }
-#endif
-
-class XML_Node {
-public:
-
- XMP_Uns8 kind;
- XMP_VarString ns, name, value;
- XML_Node * parent;
- XML_NodeVector attrs;
- XML_NodeVector content;
- #if 0 // *** XMP_DebugBuild
- XMP_StringPtr _namePtr, _valuePtr; // *** Not working, need operator=?
- #endif
-
- XML_Node ( XML_Node * _parent, XMP_StringPtr _name, XMP_Uns8 _kind )
- : kind(_kind), name(_name), parent(_parent)
- {
- #if 0 // *** XMP_DebugBuild
- _namePtr = name.c_str();
- _valuePtr = value.c_str();
- #endif
- };
-
- XML_Node ( XML_Node * _parent, const XMP_VarString & _name, XMP_Uns8 _kind )
- : kind(_kind), name(_name), parent(_parent)
- {
- #if 0 // *** XMP_DebugBuild
- _namePtr = name.c_str();
- _valuePtr = value.c_str();
- #endif
- };
-
- void RemoveAttrs()
- {
- for ( size_t i = 0, vLim = attrs.size(); i < vLim; ++i ) delete attrs[i];
- attrs.clear();
- }
-
- void RemoveContent()
- {
- for ( size_t i = 0, vLim = content.size(); i < vLim; ++i ) delete content[i];
- content.clear();
- }
-
- void ClearNode()
- {
- kind = 0;
- ns.erase();
- name.erase();
- value.erase();
- this->RemoveAttrs();
- this->RemoveContent();
- }
-
- virtual ~XML_Node() { RemoveAttrs(); RemoveContent(); }
-
-private:
- XML_Node() : kind(0), parent(0) // ! Make sure parent pointer is always set.
- {
- #if 0 // *** XMP_DebugBuild
- _namePtr = name.c_str();
- _valuePtr = value.c_str();
- #endif
- };
-
-};
-
extern void ProcessRDF ( XMP_Node * xmpTree, const XML_Node & xmlTree, XMP_OptionBits options );
// =================================================================================================
diff --git a/source/XMPCore/XMPMeta-GetSet.cpp b/source/XMPCore/XMPMeta-GetSet.cpp
index b564b37..41b01d5 100644
--- a/source/XMPCore/XMPMeta-GetSet.cpp
+++ b/source/XMPCore/XMPMeta-GetSet.cpp
@@ -1,5 +1,5 @@
// =================================================================================================
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -74,8 +74,8 @@ SetNodeValue ( XMP_Node * node, XMP_StringPtr value )
{
#if XMP_DebugBuild // ! Hack to force an assert.
- if ( (node->name == "xap:TestAssertNotify") && XMP_LitMatch ( value, "DoIt!" ) ) {
- XMP_Assert ( node->name != "xap:TestAssertNotify" );
+ if ( (node->name == "xmp:TestAssertNotify") && XMP_LitMatch ( value, "DoIt!" ) ) {
+ XMP_Assert ( node->name != "xmp:TestAssertNotify" );
}
#endif
@@ -1207,3 +1207,4 @@ XMPMeta::SetProperty_Date ( XMP_StringPtr schemaNS,
} // SetProperty_Date
// =================================================================================================
+
diff --git a/source/XMPCore/XMPMeta-Parse.cpp b/source/XMPCore/XMPMeta-Parse.cpp
index 9861286..5ff64f4 100644
--- a/source/XMPCore/XMPMeta-Parse.cpp
+++ b/source/XMPCore/XMPMeta-Parse.cpp
@@ -1,5 +1,5 @@
// =================================================================================================
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -105,23 +105,16 @@ static const char * kReplaceLatin1[128] =
// -------------------------------------------------------------------------------------------------
// PickBestRoot
// ------------
-//
-// Pick the first pxmp:XMP_Packet or x:xmpmeta among multiple root candidates. If there aren't any,
-// pick the first bare rdf:RDF if that is allowed. The returned root is the rdf:RDF child if an
-// x:xmpmeta element was chosen. The search is breadth first, so a higher level candiate is chosen
-// over a lower level one that was textually earlier in the serialized XML.
-
static const XML_Node * PickBestRoot ( const XML_Node & xmlParent, XMP_OptionBits options )
{
- // Look among this parent's content for pxmp:XMP_Packet or x:xmpmeta. The recursion for
- // x:xmpmeta is broader than the strictly defined choice, but gives us smaller code.
+
+ // Look among this parent's content for x:xmpmeta. The recursion for x:xmpmeta is broader than
+ // the strictly defined choice, but gives us smaller code.
for ( size_t childNum = 0, childLim = xmlParent.content.size(); childNum < childLim; ++childNum ) {
const XML_Node * childNode = xmlParent.content[childNum];
if ( childNode->kind != kElemNode ) continue;
- if ( childNode->name == "pxmp:XMP_Packet" ) return childNode;
if ( (childNode->name == "x:xmpmeta") || (childNode->name == "x:xapmeta") ) return PickBestRoot ( *childNode, 0 );
}
-
// Look among this parent's content for a bare rdf:RDF if that is allowed.
if ( ! (options & kXMP_RequireXMPMeta) ) {
for ( size_t childNum = 0, childLim = xmlParent.content.size(); childNum < childLim; ++childNum ) {
@@ -147,8 +140,8 @@ static const XML_Node * PickBestRoot ( const XML_Node & xmlParent, XMP_OptionBit
//
// Find the XML node that is the root of the XMP data tree. Generally this will be an outer node,
// but it could be anywhere if a general XML document is parsed (e.g. SVG). The XML parser counted
-// all rdf:RDF and pxmp:XMP_Packet nodes, and kept a pointer to the last one. If there is more than
-// one possible root use PickBestRoot to choose among them.
+// all possible root nodes, and kept a pointer to the last one. If there is more than one possible
+// root use PickBestRoot to choose among them.
//
// If there is a root node, try to extract the version of the previous XMP toolkit.
@@ -163,18 +156,6 @@ static const XML_Node * FindRootNode ( XMPMeta * thiz, const XMLParserAdapter &
XMP_StringPtr verStr = "";
- if ( rootNode->name == "pxmp:XMP_Packet" ) {
-
- for ( size_t attrNum = 0, attrLim = rootNode->attrs.size(); attrNum < attrLim; ++attrNum ) {
- const XML_Node * currAttr =rootNode->attrs[attrNum];
- if ( currAttr->name == "pxmp:xmptk" ) {
- verStr = currAttr->value.c_str();
- break;
- }
- }
-
- } else {
-
XMP_Assert ( rootNode->name == "rdf:RDF" );
if ( (options & kXMP_RequireXMPMeta) &&
@@ -189,8 +170,6 @@ static const XML_Node * FindRootNode ( XMPMeta * thiz, const XMLParserAdapter &
}
}
- }
-
// Decode the version number into MMmmuubbb digits. If any part is too big, peg it at 99 or 999.
unsigned long part;
@@ -633,6 +612,7 @@ RepairAltText ( XMP_Node & tree, XMP_StringPtr schemaNS, XMP_StringPtr arrayName
if ( (arrayNode == 0) || XMP_ArrayIsAltText ( arrayNode->options ) ) return; // Already OK.
if ( ! XMP_PropIsArray ( arrayNode->options ) ) return; // ! Not even an array, leave it alone.
+ // *** Should probably change simple values to LangAlt with 'x-default' item.
arrayNode->options |= (kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate | kXMP_PropArrayIsAltText);
@@ -689,9 +669,28 @@ TouchUpDataModel ( XMPMeta * xmp )
currSchema = FindSchemaNode ( &tree, kXMP_NS_EXIF, kXMP_ExistingOnly );
if ( currSchema != 0 ) {
+
// Do a special case fix for exif:GPSTimeStamp.
XMP_Node * gpsDateTime = FindChildNode ( currSchema, "exif:GPSTimeStamp", kXMP_ExistingOnly );
if ( gpsDateTime != 0 ) FixGPSTimeStamp ( currSchema, gpsDateTime );
+
+ // *** Should probably have RepairAltText change simple values to LangAlt with 'x-default' item.
+ // *** For now just do this for exif:UserComment, the one case we know about, late in cycle fix.
+ XMP_Node * userComment = FindChildNode ( currSchema, "exif:UserComment", kXMP_ExistingOnly );
+ if ( (userComment != 0) && XMP_PropIsSimple ( userComment->options ) ) {
+ XMP_Node * newChild = new XMP_Node ( userComment, kXMP_ArrayItemName,
+ userComment->value.c_str(), userComment->options );
+ newChild->qualifiers.swap ( userComment->qualifiers );
+ if ( ! XMP_PropHasLang ( newChild->options ) ) {
+ XMP_Node * langQual = new XMP_Node ( newChild, "xml:lang", "x-default", kXMP_PropIsQualifier );
+ newChild->qualifiers.insert ( newChild->qualifiers.begin(), langQual );
+ newChild->options |= (kXMP_PropHasQualifiers | kXMP_PropHasLang);
+ }
+ userComment->value.erase();
+ userComment->options = kXMP_PropArrayFormMask; // ! Happens to have all the right bits.
+ userComment->children.push_back ( newChild );
+ }
+
}
currSchema = FindSchemaNode ( &tree, kXMP_NS_DM, kXMP_ExistingOnly );
@@ -717,7 +716,7 @@ TouchUpDataModel ( XMPMeta * xmp )
RepairAltText ( tree, kXMP_NS_DC, "dc:description" ); // ! Note inclusion of prefixes for direct node lookup!
RepairAltText ( tree, kXMP_NS_DC, "dc:rights" );
RepairAltText ( tree, kXMP_NS_DC, "dc:title" );
- RepairAltText ( tree, kXMP_NS_XMP_Rights, "xapRights:UsageTerms" );
+ RepairAltText ( tree, kXMP_NS_XMP_Rights, "xmpRights:UsageTerms" );
RepairAltText ( tree, kXMP_NS_EXIF, "exif:UserComment" );
// Tweak old XMP: Move an instance ID from rdf:about to the xmpMM:InstanceID property. An old
@@ -1087,7 +1086,7 @@ XMPMeta::ParseFromBuffer ( XMP_StringPtr buffer,
if ( this->xmlParser == 0 ) {
if ( (xmpSize == 0) && lastClientCall ) return; // Tolerate empty parse. Expat complains if there are no XML elements.
- this->xmlParser = new ExpatAdapter;
+ this->xmlParser = XMP_NewExpatAdapter();
}
XMLParserAdapter& parser = *this->xmlParser;
diff --git a/source/XMPCore/XMPMeta-Serialize.cpp b/source/XMPCore/XMPMeta-Serialize.cpp
index 73c0d2b..0aa75ae 100644
--- a/source/XMPCore/XMPMeta-Serialize.cpp
+++ b/source/XMPCore/XMPMeta-Serialize.cpp
@@ -1,5 +1,5 @@
// =================================================================================================
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -43,9 +43,6 @@ using namespace std;
static const char * kPacketHeader = "<?xpacket begin=\"\xEF\xBB\xBF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>";
static const char * kPacketTrailer = "<?xpacket end=\"w\"?>"; // ! The w/r is at [size-4].
-static const char * kPXMP_PacketStart = "<pxmp:XMP_Packet";
-static const char * kPXMP_PacketEnd = "</pxmp:XMP_Packet>";
-
static const char * kPXMP_SchemaGroup = "XMP_SchemaGroup";
static const char * kRDF_XMPMetaStart = "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"";
@@ -1106,10 +1103,12 @@ SerializeAsRDF ( const XMPMeta & xmpObj,
}
// Write the xmpmeta element's start tag.
- for ( level = baseIndent; level > 0; --level ) headStr += indentStr;
- headStr += kRDF_XMPMetaStart;
- headStr += kXMPCore_VersionMessage "\">";
- headStr += newline;
+ if ( ! (options & kXMP_OmitXMPMetaElement) ) {
+ for ( level = baseIndent; level > 0; --level ) headStr += indentStr;
+ headStr += kRDF_XMPMetaStart;
+ headStr += kXMPCore_VersionMessage "\">";
+ headStr += newline;
+ }
// Write the rdf:RDF start tag.
for ( level = baseIndent+1; level > 0; --level ) headStr += indentStr;
@@ -1141,9 +1140,11 @@ SerializeAsRDF ( const XMPMeta & xmpObj,
headStr += newline;
// Write the xmpmeta end tag.
- for ( level = baseIndent; level > 0; --level ) headStr += indentStr;
- headStr += kRDF_XMPMetaEnd;
- headStr += newline;
+ if ( ! (options & kXMP_OmitXMPMetaElement) ) {
+ for ( level = baseIndent; level > 0; --level ) headStr += indentStr;
+ headStr += kRDF_XMPMetaEnd;
+ headStr += newline;
+ }
// Write the packet trailer PI into the tail string as UTF-8.
tailStr.erase();
diff --git a/source/XMPCore/XMPMeta.cpp b/source/XMPCore/XMPMeta.cpp
index 888d48e..c5def31 100644
--- a/source/XMPCore/XMPMeta.cpp
+++ b/source/XMPCore/XMPMeta.cpp
@@ -1,5 +1,5 @@
// =================================================================================================
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -12,14 +12,18 @@
#include "XMP_Environment.h" // ! This must be the first include!
#include "XMPCore_Impl.hpp"
+#include <stdio.h>
+
#include "XMPMeta.hpp"
#include "XMPIterator.hpp"
#include "XMPUtils.hpp"
-
#include "XMP_Version.h"
#include "UnicodeInlines.incl_cpp"
#include "UnicodeConversions.hpp"
+#include <algorithm> // For sort and stable_sort.
+#include <stdio.h> // For snprintf.
+
#if XMP_DebugBuild
#include <iostream>
#endif
@@ -82,11 +86,66 @@ static const char * kTenSpaces = " ";
#define OutProcHexInt(num) { snprintf ( buffer, sizeof(buffer), "%X", (num) ); /* AUDIT: Using sizeof for snprintf length is safe */ \
status = (*outProc) ( refCon, buffer, strlen(buffer) ); if ( status != 0 ) goto EXIT; }
+#define OutProcHexByte(num) { snprintf ( buffer, sizeof(buffer), "%.2X", (num) ); /* AUDIT: Using sizeof for snprintf length is safe */ \
+ status = (*outProc) ( refCon, buffer, strlen(buffer) ); if ( status != 0 ) goto EXIT; }
+
static const char * kIndent = " ";
#define OutProcIndent(lev) { for ( size_t i = 0; i < (lev); ++i ) OutProcNChars ( kIndent, 3 ); }
// -------------------------------------------------------------------------------------------------
+// DumpClearString
+// ---------------
+
+static XMP_Status
+DumpClearString ( const XMP_VarString & value, XMP_TextOutputProc outProc, void * refCon )
+{
+
+ char buffer [20];
+ bool prevNormal;
+ XMP_Status status = 0;
+
+ XMP_StringPtr spanStart, spanEnd;
+ XMP_StringPtr valueEnd = &value[0] + value.size();
+
+ spanStart = &value[0];
+ while ( spanStart < valueEnd ) {
+
+ // Output the next span of regular characters.
+ for ( spanEnd = spanStart; spanEnd < valueEnd; ++spanEnd ) {
+ if ( *spanEnd > 0x7F ) break;
+ if ( (*spanEnd < 0x20) && (*spanEnd != kTab) && (*spanEnd != kLF) ) break;
+ }
+ if ( spanStart != spanEnd ) status = (*outProc) ( refCon, spanStart, (spanEnd-spanStart) );
+ if ( status != 0 ) break;
+ spanStart = spanEnd;
+
+ // Output the next span of irregular characters.
+ prevNormal = true;
+ for ( spanEnd = spanStart; spanEnd < valueEnd; ++spanEnd ) {
+ if ( ((0x20 <= *spanEnd) && (*spanEnd <= 0x7F)) || (*spanEnd == kTab) || (*spanEnd == kLF) ) break;
+ char space = ' ';
+ if ( prevNormal ) space = '<';
+ status = (*outProc) ( refCon, &space, 1 );
+ if ( status != 0 ) break;
+ OutProcHexByte ( *spanEnd );
+ prevNormal = false;
+ }
+ if ( ! prevNormal ) {
+ status = (*outProc) ( refCon, ">", 1 );
+ if ( status != 0 ) return status;
+ }
+ spanStart = spanEnd;
+
+ }
+
+EXIT:
+ return status;
+
+} // DumpClearString
+
+
+// -------------------------------------------------------------------------------------------------
// DumpStringMap
// -------------
@@ -109,10 +168,10 @@ DumpStringMap ( const XMP_StringMap & map, XMP_StringPtr label, XMP_TextOutputPr
for ( currPos = map.begin(); currPos != endPos; ++currPos ) {
OutProcNChars ( " ", 2 );
- OutProcString ( currPos->first );
+ DumpClearString ( currPos->first, outProc, refCon );
OutProcPadding ( maxLen - currPos->first.size() );
OutProcNChars ( " => ", 4 );
- OutProcString ( currPos->second );
+ DumpClearString ( currPos->second, outProc, refCon );
OutProcNewline();
}
@@ -211,7 +270,7 @@ DumpPropertyTree ( const XMP_Node * currNode,
OutProcIndent ( (size_t)indent );
if ( itemIndex == 0 ) {
if ( currNode->options & kXMP_PropIsQualifier ) OutProcNChars ( "? ", 2 );
- OutProcString ( currNode->name );
+ DumpClearString ( currNode->name, outProc, refCon );
} else {
OutProcNChars ( "[", 1 );
OutProcDecInt ( itemIndex );
@@ -220,7 +279,7 @@ DumpPropertyTree ( const XMP_Node * currNode,
if ( ! (currNode->options & kXMP_PropCompositeMask) ) {
OutProcNChars ( " = \"", 4 );
- OutProcString ( currNode->value );
+ DumpClearString ( currNode->value, outProc, refCon );
OutProcNChars ( "\"", 1 );
}
@@ -408,6 +467,114 @@ static void DumpXMLTree ( FILE * log, const XML_Node & node, int indent )
#endif // DumpXMLParseTree
+// -------------------------------------------------------------------------------------------------
+// CompareNodeNames
+// ----------------
+//
+// Comparison routine for sorting XMP nodes by name. The name "xml:lang" is less than anything else,
+// and "rdf:type" is less than anything except "xml:lang". This preserves special rules for qualifiers.
+
+static bool
+CompareNodeNames ( XMP_Node * left, XMP_Node * right )
+{
+
+ if ( left->name == "xml:lang" ) return true;
+ if ( right->name == "xml:lang" ) return false;
+
+ if ( left->name == "rdf:type" ) return true;
+ if ( right->name == "rdf:type" ) return false;
+
+ return ( left->name < right->name );
+
+} // CompareNodeNames
+
+
+// -------------------------------------------------------------------------------------------------
+// CompareNodeValues
+// -----------------
+//
+// Comparison routine for sorting XMP nodes by value.
+
+static bool
+CompareNodeValues ( XMP_Node * left, XMP_Node * right )
+{
+
+ if ( XMP_PropIsSimple ( left->options ) && XMP_PropIsSimple ( right->options ) ) {
+ return ( left->value < right->value );
+ }
+
+ XMP_OptionBits leftForm = left->options & kXMP_PropCompositeMask;
+ XMP_OptionBits rightForm = right->options & kXMP_PropCompositeMask;
+
+ return ( leftForm < rightForm );
+
+} // CompareNodeValues
+
+
+// -------------------------------------------------------------------------------------------------
+// CompareNodeLangs
+// ----------------
+//
+// Comparison routine for sorting XMP nodes by xml:lang qualifier. An "x-default" value is less than
+// any other language.
+
+static bool
+CompareNodeLangs ( XMP_Node * left, XMP_Node * right )
+{
+
+ if ( left->qualifiers.empty() || (left->qualifiers[0]->name != "xml:lang") ) return false;
+ if ( right->qualifiers.empty() || (right->qualifiers[0]->name != "xml:lang") ) return false;
+
+ if ( left->qualifiers[0]->value == "x-default" ) return true;
+ if ( right->qualifiers[0]->value == "x-default" ) return false;
+
+ return ( left->qualifiers[0]->value < right->qualifiers[0]->value );
+
+} // CompareNodeLangs
+
+
+// -------------------------------------------------------------------------------------------------
+// SortWithinOffspring
+// -------------------
+//
+// Sort one level down, within the elements of a node vector. This sorts the qualifiers of each
+// node. If the node is a struct it sorts the fields by names. If the node is an unordered array it
+// sorts the elements by value. If the node is an AltText array it sorts the elements by language.
+
+static void
+SortWithinOffspring ( XMP_NodeOffspring & nodeVec )
+{
+
+ for ( size_t i = 0, limit = nodeVec.size(); i < limit; ++i ) {
+
+ XMP_Node * currPos = nodeVec[i];
+
+ if ( ! currPos->qualifiers.empty() ) {
+ sort ( currPos->qualifiers.begin(), currPos->qualifiers.end(), CompareNodeNames );
+ SortWithinOffspring ( currPos->qualifiers );
+ }
+
+ if ( ! currPos->children.empty() ) {
+
+ if ( XMP_PropIsStruct ( currPos->options ) || XMP_NodeIsSchema ( currPos->options ) ) {
+ sort ( currPos->children.begin(), currPos->children.end(), CompareNodeNames );
+ } else if ( XMP_PropIsArray ( currPos->options ) ) {
+ if ( XMP_ArrayIsUnordered ( currPos->options ) ) {
+ stable_sort ( currPos->children.begin(), currPos->children.end(), CompareNodeValues );
+ } else if ( XMP_ArrayIsAltText ( currPos->options ) ) {
+ sort ( currPos->children.begin(), currPos->children.end(), CompareNodeLangs );
+ }
+ }
+
+ SortWithinOffspring ( currPos->children );
+
+ }
+
+ }
+
+} // SortWithinOffspring
+
+
// =================================================================================================
// Constructors
// ============
@@ -451,7 +618,8 @@ XMPMeta::~XMPMeta() RELEASE_NO_THROW
XMPMeta::GetVersionInfo ( XMP_VersionInfo * info )
{
- memset ( info, 0, sizeof(XMP_VersionInfo) );
+ memset ( info, 0, sizeof(*info) ); // AUDIT: Safe, using sizeof the destination.
+ XMP_Assert ( sizeof(*info) == sizeof(XMP_VersionInfo) );
info->major = XMP_API_VERSION_MAJOR;
info->minor = XMP_API_VERSION_MINOR;
@@ -501,7 +669,7 @@ XMPMeta::Initialize()
(void) RegisterNamespace ( kXMP_NS_RDF, "rdf", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_DC, "dc", &voidPtr, &voidLen );
- (void) RegisterNamespace ( kXMP_NS_XMP, "xap", &voidPtr, &voidLen );
+ (void) RegisterNamespace ( kXMP_NS_XMP, "xmp", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_PDF, "pdf", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_Photoshop, "photoshop", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_PSAlbum, "album", &voidPtr, &voidLen );
@@ -516,17 +684,18 @@ XMPMeta::Initialize()
(void) RegisterNamespace ( kXMP_NS_WAV, "wav", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_AdobeStockPhoto, "bmsp", &voidPtr, &voidLen );
+ (void) RegisterNamespace ( kXMP_NS_CreatorAtom, "creatorAtom", &voidPtr, &voidLen );
- (void) RegisterNamespace ( kXMP_NS_XMP_Rights, "xapRights", &voidPtr, &voidLen );
- (void) RegisterNamespace ( kXMP_NS_XMP_MM, "xapMM", &voidPtr, &voidLen );
- (void) RegisterNamespace ( kXMP_NS_XMP_BJ, "xapBJ", &voidPtr, &voidLen );
+ (void) RegisterNamespace ( kXMP_NS_XMP_Rights, "xmpRights", &voidPtr, &voidLen );
+ (void) RegisterNamespace ( kXMP_NS_XMP_MM, "xmpMM", &voidPtr, &voidLen );
+ (void) RegisterNamespace ( kXMP_NS_XMP_BJ, "xmpBJ", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_XMP_Note, "xmpNote", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_DM, "xmpDM", &voidPtr, &voidLen );
- (void) RegisterNamespace ( kXMP_NS_XMP_Text, "xapT", &voidPtr, &voidLen );
- (void) RegisterNamespace ( kXMP_NS_XMP_PagedFile, "xapTPg", &voidPtr, &voidLen );
- (void) RegisterNamespace ( kXMP_NS_XMP_Graphics, "xapG", &voidPtr, &voidLen );
- (void) RegisterNamespace ( kXMP_NS_XMP_Image, "xapGImg", &voidPtr, &voidLen );
+ (void) RegisterNamespace ( kXMP_NS_XMP_Text, "xmpT", &voidPtr, &voidLen );
+ (void) RegisterNamespace ( kXMP_NS_XMP_PagedFile, "xmpTPg", &voidPtr, &voidLen );
+ (void) RegisterNamespace ( kXMP_NS_XMP_Graphics, "xmpG", &voidPtr, &voidLen );
+ (void) RegisterNamespace ( kXMP_NS_XMP_Image, "xmpGImg", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_XMP_Font, "stFnt", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_XMP_Dimensions, "stDim", &voidPtr, &voidLen );
@@ -539,6 +708,7 @@ XMPMeta::Initialize()
(void) RegisterNamespace ( kXMP_NS_XMP_IdentifierQual, "xmpidq", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_IPTCCore, "Iptc4xmpCore", &voidPtr, &voidLen );
+ (void) RegisterNamespace ( kXMP_NS_DICOM, "DICOM", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_PDFA_Schema, "pdfaSchema", &voidPtr, &voidLen );
(void) RegisterNamespace ( kXMP_NS_PDFA_Property, "pdfaProperty", &voidPtr, &voidLen );
@@ -559,7 +729,6 @@ XMPMeta::Initialize()
if ( ! XMPIterator::Initialize() ) XMP_Throw ( "Failure from XMPIterator::Initialize", kXMPErr_InternalFailure );
if ( ! XMPUtils::Initialize() ) XMP_Throw ( "Failure from XMPUtils::Initialize", kXMPErr_InternalFailure );
-
// Do miscelaneous semantic checks of types and arithmetic.
XMP_Assert ( sizeof(XMP_Int8) == 1 );
@@ -649,7 +818,6 @@ XMPMeta::Terminate() RELEASE_NO_THROW
XMPIterator::Terminate();
XMPUtils::Terminate();
-
EliminateGlobal ( sNamespaceURIToPrefixMap );
EliminateGlobal ( sNamespacePrefixToURIMap );
EliminateGlobal ( sRegisteredAliasMap );
@@ -660,7 +828,7 @@ XMPMeta::Terminate() RELEASE_NO_THROW
EliminateGlobal ( sExceptionMessage );
XMP_TermMutex ( sXMPCoreLock );
-
+
} // Terminate
@@ -710,7 +878,7 @@ XMPMeta::DumpNamespaces ( XMP_TextOutputProc outProc,
void * refCon )
{
XMP_Assert ( outProc != 0 ); // ! Enforced by wrapper.
- XMP_Status status;
+ XMP_Status status = 0;
XMP_StringMapPos p2uEnd = sNamespacePrefixToURIMap->end(); // ! Move up to avoid gcc complaints.
XMP_StringMapPos u2pEnd = sNamespaceURIToPrefixMap->end();
@@ -728,7 +896,7 @@ XMPMeta::DumpNamespaces ( XMP_TextOutputProc outProc,
XMP_StringMapPos nsOther = sNamespaceURIToPrefixMap->find ( nsLeft->second );
if ( (nsOther == u2pEnd) || (nsLeft != sNamespacePrefixToURIMap->find ( nsOther->second )) ) {
OutProcLiteral ( " ** bad namespace URI ** " );
- OutProcString ( nsLeft->second );
+ DumpClearString ( nsLeft->second, outProc, refCon );
goto FAILURE;
}
@@ -736,7 +904,7 @@ XMPMeta::DumpNamespaces ( XMP_TextOutputProc outProc,
if ( nsRight == nsLeft ) continue; // ! Can't start at nsLeft+1, no operator+!
if ( nsLeft->second == nsRight->second ) {
OutProcLiteral ( " ** duplicate namespace URI ** " );
- OutProcString ( nsLeft->second );
+ DumpClearString ( nsLeft->second, outProc, refCon );
goto FAILURE;
}
}
@@ -748,7 +916,7 @@ XMPMeta::DumpNamespaces ( XMP_TextOutputProc outProc,
XMP_StringMapPos nsOther = sNamespacePrefixToURIMap->find ( nsLeft->second );
if ( (nsOther == p2uEnd) || (nsLeft != sNamespaceURIToPrefixMap->find ( nsOther->second )) ) {
OutProcLiteral ( " ** bad namespace prefix ** " );
- OutProcString ( nsLeft->second );
+ DumpClearString ( nsLeft->second, outProc, refCon );
goto FAILURE;
}
@@ -756,7 +924,7 @@ XMPMeta::DumpNamespaces ( XMP_TextOutputProc outProc,
if ( nsRight == nsLeft ) continue; // ! Can't start at nsLeft+1, no operator+!
if ( nsLeft->second == nsRight->second ) {
OutProcLiteral ( " ** duplicate namespace prefix ** " );
- OutProcString ( nsLeft->second );
+ DumpClearString ( nsLeft->second, outProc, refCon );
goto FAILURE;
}
}
@@ -784,7 +952,7 @@ XMPMeta::DumpAliases ( XMP_TextOutputProc outProc,
void * refCon )
{
XMP_Assert ( outProc != 0 ); // ! Enforced by wrapper.
- XMP_Status status;
+ XMP_Status status = 0;
XMP_Assert ( sRegisteredAliasMap != 0 );
@@ -803,7 +971,7 @@ XMPMeta::DumpAliases ( XMP_TextOutputProc outProc,
for ( aliasPos = sRegisteredAliasMap->begin(); aliasPos != aliasEnd; ++aliasPos ) {
OutProcNChars ( " ", 3 );
- OutProcString ( aliasPos->first );
+ DumpClearString ( aliasPos->first, outProc, refCon );
OutProcPadding ( maxLen - aliasPos->first.size() );
OutProcNChars ( " => ", 4 );
@@ -986,13 +1154,21 @@ XMPMeta::GetNamespaceURI ( XMP_StringPtr namespacePrefix,
// ---------------
// *** Don't allow standard namespaces to be deleted.
+// *** We would be better off not having this. Instead, have local namespaces from parsing be
+// *** restricted to the object that introduced them.
/* class-static */ void
XMPMeta::DeleteNamespace ( XMP_StringPtr namespaceURI )
{
- namespaceURI = namespaceURI; // Avoid unused parameter warning.
- XMP_Assert ( *namespaceURI != 0 ); // ! Enforced by wrapper.
- XMP_Throw ( "Unimplemented method XMPMeta::DeleteNamespace", kXMPErr_Unimplemented ); // *** #error "write me"
+
+ XMP_StringMapPos uriPos = sNamespaceURIToPrefixMap->find ( namespaceURI );
+ if ( uriPos == sNamespaceURIToPrefixMap->end() ) return;
+
+ XMP_StringMapPos prefixPos = sNamespacePrefixToURIMap->find ( uriPos->second );
+ XMP_Assert ( prefixPos != sNamespacePrefixToURIMap->end() );
+
+ sNamespaceURIToPrefixMap->erase ( uriPos );
+ sNamespacePrefixToURIMap->erase ( prefixPos );
} // DeleteNamespace
@@ -1287,10 +1463,10 @@ XMPMeta::DumpObject ( XMP_TextOutputProc outProc,
void * refCon ) const
{
XMP_Assert ( outProc != 0 ); // ! Enforced by wrapper.
- XMP_Status status;
+ XMP_Status status = 0;
OutProcLiteral ( "Dumping XMPMeta object \"" );
- OutProcString ( tree.name );
+ DumpClearString ( tree.name, outProc, refCon );
OutProcNChars ( "\" ", 3 );
status = DumpNodeOptions ( tree.options, outProc, refCon );
if ( status != 0 ) goto EXIT;
@@ -1302,7 +1478,7 @@ XMPMeta::DumpObject ( XMP_TextOutputProc outProc,
if ( ! tree.value.empty() ) {
OutProcLiteral ( "** bad root value ** \"" );
- OutProcString ( tree.value );
+ DumpClearString ( tree.value, outProc, refCon );
OutProcNChars ( "\"", 1 );
OutProcNewline();
}
@@ -1323,9 +1499,9 @@ XMPMeta::DumpObject ( XMP_TextOutputProc outProc,
OutProcNewline();
OutProcIndent ( 1 );
- OutProcString ( currSchema->name );
+ DumpClearString ( currSchema->value, outProc, refCon );
OutProcNChars ( " ", 2 );
- OutProcString ( currSchema->value );
+ DumpClearString ( currSchema->name, outProc, refCon );
OutProcNChars ( " ", 2 );
status = DumpNodeOptions ( currSchema->options, outProc, refCon );
if ( status != 0 ) goto EXIT;
@@ -1441,20 +1617,68 @@ XMPMeta::SetObjectOptions ( XMP_OptionBits options )
// -------------------------------------------------------------------------------------------------
+// Sort
+// ----
+//
+// At the top level the namespaces are sorted by their prefixes. Within a namespace, the top level
+// properties are sorted by name. Within a struct, the fields are sorted by their qualified name,
+// i.e. their XML prefix:local form. Unordered arrays of simple items are sorted by value. Language
+// Alternative arrays are sorted by the xml:lang qualifiers, with the "x-default" item placed first.
+
+void
+XMPMeta::Sort()
+{
+
+ if ( ! this->tree.qualifiers.empty() ) {
+ sort ( this->tree.qualifiers.begin(), this->tree.qualifiers.end(), CompareNodeNames );
+ SortWithinOffspring ( this->tree.qualifiers );
+ }
+
+ if ( ! this->tree.children.empty() ) {
+ // The schema prefixes are the node's value, the name is the URI, so we sort schemas by value.
+ sort ( this->tree.children.begin(), this->tree.children.end(), CompareNodeValues );
+ SortWithinOffspring ( this->tree.children );
+ }
+
+} // Sort
+
+
+// -------------------------------------------------------------------------------------------------
+// Erase
+// -----
+//
+// Clear everything except for clientRefs.
+
+void
+XMPMeta::Erase()
+{
+
+ this->prevTkVer = 0;
+ if ( this->xmlParser != 0 ) {
+ delete ( this->xmlParser );
+ this->xmlParser = 0;
+ }
+ this->tree.ClearNode();
+
+} // Erase
+
+
+// -------------------------------------------------------------------------------------------------
// Clone
// -----
-XMPMeta *
-XMPMeta::Clone ( XMP_OptionBits options ) const
+void
+XMPMeta::Clone ( XMPMeta * clone, XMP_OptionBits options ) const
{
+ if ( clone == 0 ) XMP_Throw ( "Null clone pointer", kXMPErr_BadParam );
if ( options != 0 ) XMP_Throw ( "No options are defined yet", kXMPErr_BadOptions );
-
- XMPMeta * clone = new XMPMeta;
+ XMP_Assert ( this->tree.parent == 0 );
+
+ clone->tree.ClearNode();
clone->tree.options = this->tree.options;
clone->tree.name = this->tree.name;
clone->tree.value = this->tree.value;
- XMP_Assert ( this->tree.parent == 0 );
#if 0 // *** XMP_DebugBuild
clone->tree._namePtr = clone->tree.name.c_str();
@@ -1463,9 +1687,6 @@ XMPMeta::Clone ( XMP_OptionBits options ) const
CloneOffspring ( &this->tree, &clone->tree );
- XMP_Assert ( clone->clientRefs == 0 ); // Gets incremneted later.
- return clone;
-
} // Clone
// =================================================================================================
diff --git a/source/XMPCore/XMPMeta.hpp b/source/XMPCore/XMPMeta.hpp
index a73eb6a..dca2c2a 100644
--- a/source/XMPCore/XMPMeta.hpp
+++ b/source/XMPCore/XMPMeta.hpp
@@ -2,7 +2,7 @@
#define __XMPMeta_hpp__
// =================================================================================================
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -335,8 +335,14 @@ public:
void
SetObjectOptions ( XMP_OptionBits options );
- XMPMeta *
- Clone ( XMP_OptionBits options ) const;
+ void
+ Sort();
+
+ void
+ Erase();
+
+ void
+ Clone ( XMPMeta * clone, XMP_OptionBits options ) const;
XMP_Index
CountArrayItems ( XMP_StringPtr schemaNS,
diff --git a/source/XMPCore/XMPUtils-FileInfo.cpp b/source/XMPCore/XMPUtils-FileInfo.cpp
index 7bf3836..ebfb9a5 100644
--- a/source/XMPCore/XMPUtils-FileInfo.cpp
+++ b/source/XMPCore/XMPUtils-FileInfo.cpp
@@ -1,5 +1,5 @@
// =================================================================================================
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -23,7 +23,6 @@
#pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning)
#endif
-
// =================================================================================================
// Local Types and Constants
// =========================
@@ -52,7 +51,6 @@ typedef enum UniCharKind UniCharKind;
// Static Variables
// ================
-
// =================================================================================================
// Local Utilities
// ===============
@@ -327,6 +325,7 @@ CodePointToUTF8 ( UniCodePoint uniChar, XMP_VarString & utf8Str )
cpTemp = cpTemp >> 6;
}
byteCount = 8 - i; // The total number of bytes needed.
+ XMP_Assert ( (2 <= byteCount) && (byteCount <= 6) );
// -------------------------------------------------------------------------------------
// Make sure the high order byte can hold the byte count mask, compute and set the mask.
@@ -336,7 +335,8 @@ CodePointToUTF8 ( UniCodePoint uniChar, XMP_VarString & utf8Str )
if ( bitCount > (8 - (byteCount + 1)) ) byteCount += 1;
i = 8 - byteCount; // First byte index and mask shift count.
- buffer[i] |= (UnsByte(0xFF) << i) & UnsByte(0xFF);
+ XMP_Assert ( (0 <= i) && (i <= 6) );
+ buffer[i] |= (UnsByte(0xFF) << i) & UnsByte(0xFF); // AUDIT: Safe, i is between 0 and 6.
}
@@ -447,12 +447,12 @@ IsInternalProperty ( const XMP_VarString & schema, const XMP_VarString & prop )
} else if ( schema == kXMP_NS_XMP ) {
- if ( (prop == "xap:BaseURL") ||
- (prop == "xap:CreatorTool") ||
- (prop == "xap:Format") ||
- (prop == "xap:Locale") ||
- (prop == "xap:MetadataDate") ||
- (prop == "xap:ModifyDate") ) {
+ if ( (prop == "xmp:BaseURL") ||
+ (prop == "xmp:CreatorTool") ||
+ (prop == "xmp:Format") ||
+ (prop == "xmp:Locale") ||
+ (prop == "xmp:MetadataDate") ||
+ (prop == "xmp:ModifyDate") ) {
isInternal = true;
}
@@ -1202,6 +1202,14 @@ XMPUtils::DuplicateSubtree ( const XMPMeta & source,
{
options = options; // Avoid unused parameter warning.
+ bool fullSourceTree = false;
+ bool fullDestTree = false;
+
+ XMP_ExpandedXPath sourcePath, destPath;
+
+ const XMP_Node * sourceNode = 0;
+ XMP_Node * destNode = 0;
+
XMP_Assert ( (sourceNS != 0) && (*sourceNS != 0) );
XMP_Assert ( (sourceRoot != 0) && (*sourceRoot != 0) );
XMP_Assert ( (dest != 0) && (destNS != 0) && (destRoot != 0) );
@@ -1209,47 +1217,126 @@ XMPUtils::DuplicateSubtree ( const XMPMeta & source,
if ( *destNS == 0 ) destNS = sourceNS;
if ( *destRoot == 0 ) destRoot = sourceRoot;
- if ( (&source == dest) &&
- XMP_LitMatch ( sourceNS, destNS ) &&
- XMP_LitMatch ( sourceRoot, destRoot ) ) XMP_Throw ( "Can't duplicate subtree onto itself", kXMPErr_BadParam );
-
- // Find the root nodes for the source and destination subtrees.
+ if ( XMP_LitMatch ( sourceNS, "*" ) ) fullSourceTree = true;
+ if ( XMP_LitMatch ( destNS, "*" ) ) fullDestTree = true;
- XMP_ExpandedXPath sourcePath, destPath;
- ExpandXPath ( sourceNS, sourceRoot, &sourcePath );
- ExpandXPath ( destNS, destRoot, &destPath );
-
- XMP_Node * sourceNode = FindConstNode ( &source.tree, sourcePath );
- if ( sourceNode == 0 ) XMP_Throw ( "Can't find source subtree", kXMPErr_BadXPath );
-
- XMP_Node * destNode = FindNode ( &dest->tree, destPath, kXMP_ExistingOnly ); // Dest must not yet exist.
- if ( destNode != 0 ) XMP_Throw ( "Destination subtree must not exist", kXMPErr_BadXPath );
+ if ( (&source == dest) && (fullSourceTree | fullDestTree) ) {
+ XMP_Throw ( "Can't duplicate tree onto itself", kXMPErr_BadParam );
+ }
- destNode = FindNode ( &dest->tree, destPath, kXMP_CreateNodes ); // Now create the dest.
- if ( destNode == 0 ) XMP_Throw ( "Can't create destination root node", kXMPErr_BadXPath );
+ if ( fullSourceTree & fullDestTree ) XMP_Throw ( "Use Clone for full tree to full tree", kXMPErr_BadParam );
+
+ if ( fullSourceTree ) {
- // Make sure the destination is not within the source! The source can't be inside the destination
- // because the source already existed and the destination was just created.
+ // The destination must be an existing empty struct, copy all of the source top level as fields.
+
+ ExpandXPath ( destNS, destRoot, &destPath );
+ destNode = FindNode ( &dest->tree, destPath, kXMP_ExistingOnly );
+
+ if ( (destNode == 0) || (! XMP_PropIsStruct ( destNode->options )) ) {
+ XMP_Throw ( "Destination must be an existing struct", kXMPErr_BadXPath );
+ }
+
+ if ( ! destNode->children.empty() ) {
+ if ( options & kXMP_DeleteExisting ) {
+ destNode->RemoveChildren();
+ } else {
+ XMP_Throw ( "Destination must be an empty struct", kXMPErr_BadXPath );
+ }
+ }
+
+ for ( size_t schemaNum = 0, schemaLim = source.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) {
+
+ const XMP_Node * currSchema = source.tree.children[schemaNum];
+
+ for ( size_t propNum = 0, propLim = currSchema->children.size(); propNum < propLim; ++propNum ) {
+ sourceNode = currSchema->children[propNum];
+ XMP_Node * copyNode = new XMP_Node ( destNode, sourceNode->name, sourceNode->value, sourceNode->options );
+ destNode->children.push_back ( copyNode );
+ CloneOffspring ( sourceNode, copyNode );
+ }
+
+ }
- if ( &source == dest ) {
- for ( XMP_Node * testNode = destNode; testNode != 0; testNode = testNode->parent ) {
- if ( testNode == sourceNode ) {
- // *** delete the just-created dest root node
- XMP_Throw ( "Destination subtree is within the source subtree", kXMPErr_BadXPath );
+ } else if ( fullDestTree ) {
+
+ // The source node must be an existing struct, copy all of the fields to the dest top level.
+
+ XMP_ExpandedXPath sourcePath;
+ ExpandXPath ( sourceNS, sourceRoot, &sourcePath );
+ sourceNode = FindConstNode ( &source.tree, sourcePath );
+
+ if ( (sourceNode == 0) || (! XMP_PropIsStruct ( sourceNode->options )) ) {
+ XMP_Throw ( "Source must be an existing struct", kXMPErr_BadXPath );
+ }
+
+ destNode = &dest->tree;
+
+ if ( ! destNode->children.empty() ) {
+ if ( options & kXMP_DeleteExisting ) {
+ destNode->RemoveChildren();
+ } else {
+ XMP_Throw ( "Destination tree must be empty", kXMPErr_BadXPath );
}
}
- }
+
+ std::string nsPrefix;
+ XMP_StringPtr nsURI;
+ XMP_StringLen nsLen;
+
+ for ( size_t fieldNum = 0, fieldLim = sourceNode->children.size(); fieldNum < fieldLim; ++fieldNum ) {
+
+ const XMP_Node * currField = sourceNode->children[fieldNum];
+
+ size_t colonPos = currField->name.find ( ':' );
+ nsPrefix.assign ( currField->name.c_str(), colonPos );
+ bool nsOK = XMPMeta::GetNamespaceURI ( nsPrefix.c_str(), &nsURI, &nsLen );
+ if ( ! nsOK ) XMP_Throw ( "Source field namespace is not global", kXMPErr_BadSchema );
+
+ XMP_Node * destSchema = FindSchemaNode ( &dest->tree, nsURI, kXMP_CreateNodes );
+ if ( destSchema == 0 ) XMP_Throw ( "Failed to find destination schema", kXMPErr_BadSchema );
+
+ XMP_Node * copyNode = new XMP_Node ( destSchema, currField->name, currField->value, currField->options );
+ destSchema->children.push_back ( copyNode );
+ CloneOffspring ( currField, copyNode );
+
+ }
+
+ } else {
+
+ // Find the root nodes for the source and destination subtrees.
+
+ ExpandXPath ( sourceNS, sourceRoot, &sourcePath );
+ ExpandXPath ( destNS, destRoot, &destPath );
- // All OK, copy the subtree.
- // *** Could use a CloneTree util here and maybe elsewhere.
+ sourceNode = FindConstNode ( &source.tree, sourcePath );
+ if ( sourceNode == 0 ) XMP_Throw ( "Can't find source subtree", kXMPErr_BadXPath );
+
+ destNode = FindNode ( &dest->tree, destPath, kXMP_ExistingOnly ); // Dest must not yet exist.
+ if ( destNode != 0 ) XMP_Throw ( "Destination subtree must not exist", kXMPErr_BadXPath );
+
+ destNode = FindNode ( &dest->tree, destPath, kXMP_CreateNodes ); // Now create the dest.
+ if ( destNode == 0 ) XMP_Throw ( "Can't create destination root node", kXMPErr_BadXPath );
+
+ // Make sure the destination is not within the source! The source can't be inside the destination
+ // because the source already existed and the destination was just created.
+
+ if ( &source == dest ) {
+ for ( XMP_Node * testNode = destNode; testNode != 0; testNode = testNode->parent ) {
+ if ( testNode == sourceNode ) {
+ // *** delete the just-created dest root node
+ XMP_Throw ( "Destination subtree is within the source subtree", kXMPErr_BadXPath );
+ }
+ }
+ }
- destNode->value = sourceNode->value; // *** Should use SetNode.
- destNode->options = sourceNode->options;
- CloneOffspring ( sourceNode, destNode );
+ // *** Could use a CloneTree util here and maybe elsewhere.
+
+ destNode->value = sourceNode->value; // *** Should use SetNode.
+ destNode->options = sourceNode->options;
+ CloneOffspring ( sourceNode, destNode );
- #if 0 // *** XMP_DebugBuild
- destNode->_valuePtr = destNode->value.c_str();
- #endif
+ }
} // DuplicateSubtree
diff --git a/source/XMPCore/XMPUtils.cpp b/source/XMPCore/XMPUtils.cpp
index 0dacc0c..0cfe497 100644
--- a/source/XMPCore/XMPUtils.cpp
+++ b/source/XMPCore/XMPUtils.cpp
@@ -702,8 +702,8 @@ XMPUtils::ComposeArrayItemPath ( XMP_StringPtr schemaNS,
sComposedPath->append ( reserveLen, ' ' );
if ( itemIndex != kXMP_ArrayLastItem ) {
- // AUDIT: Using string->capacity() for the snprintf length is safe.
- snprintf ( const_cast<char*>(sComposedPath->c_str()), sComposedPath->capacity(), "%s[%d]", arrayName, itemIndex );
+ // AUDIT: Using string->size() for the snprintf length is safe.
+ snprintf ( const_cast<char*>(sComposedPath->c_str()), sComposedPath->size(), "%s[%d]", arrayName, itemIndex );
} else {
*sComposedPath = arrayName;
*sComposedPath += "[last()] ";
@@ -921,8 +921,8 @@ XMPUtils::ConvertFromInt ( XMP_Int32 binValue,
sConvertedValue->reserve ( 100 ); // More than enough for any reasonable format and value.
sConvertedValue->append ( 100, ' ' );
- // AUDIT: Using string->capacity() for the snprintf length is safe.
- snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->capacity(), format, binValue );
+ // AUDIT: Using string->size() for the snprintf length is safe.
+ snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->size(), format, binValue );
*strValue = sConvertedValue->c_str();
*strSize = strlen ( *strValue ); // ! Don't use sConvertedValue->size()!
@@ -944,14 +944,14 @@ XMPUtils::ConvertFromInt64 ( XMP_Int64 binValue,
{
XMP_Assert ( (format != 0) && (strValue != 0) && (strSize != 0) ); // Enforced by wrapper.
- if ( *format == 0 ) format = "%d";
+ if ( *format == 0 ) format = "%lld";
sConvertedValue->erase();
sConvertedValue->reserve ( 100 ); // More than enough for any reasonable format and value.
sConvertedValue->append ( 100, ' ' );
- // AUDIT: Using string->capacity() for the snprintf length is safe.
- snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->capacity(), format, binValue );
+ // AUDIT: Using string->size() for the snprintf length is safe.
+ snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->size(), format, binValue );
*strValue = sConvertedValue->c_str();
*strSize = strlen ( *strValue ); // ! Don't use sConvertedValue->size()!
@@ -979,8 +979,8 @@ XMPUtils::ConvertFromFloat ( double binValue,
sConvertedValue->reserve ( 1000 ); // More than enough for any reasonable format and value.
sConvertedValue->append ( 1000, ' ' );
- // AUDIT: Using string->capacity() for the snprintf length is safe.
- snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->capacity(), format, binValue );
+ // AUDIT: Using string->size() for the snprintf length is safe.
+ snprintf ( const_cast<char*>(sConvertedValue->c_str()), sConvertedValue->size(), format, binValue );
*strValue = sConvertedValue->c_str();
*strSize = strlen ( *strValue ); // ! Don't use sConvertedValue->size()!
@@ -1167,17 +1167,17 @@ XMPUtils::ConvertToInt ( XMP_StringPtr strValue )
{
if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue );
- errno = 0;
- char * numEnd;
+ int count;
+ char nextCh;
XMP_Int32 result;
- if ( strncmp ( strValue, "0x", 2 ) != 0 ) {
- result = strtol ( strValue, &numEnd, 0 );
+ if ( ! XMP_LitNMatch ( strValue, "0x", 2 ) ) {
+ count = sscanf ( strValue, "%d%c", &result, &nextCh );
} else {
- result = (XMP_Int32) strtoul ( strValue, &numEnd, 0 ); // Treat hex form as raw bits.
+ count = sscanf ( strValue, "%x%c", &result, &nextCh );
}
- if ( (errno != 0) || (*numEnd != 0) ) XMP_Throw ( "Invalid integer string", kXMPErr_BadParam );
+ if ( count != 1 ) XMP_Throw ( "Invalid integer string", kXMPErr_BadParam );
return result;
@@ -1193,17 +1193,17 @@ XMPUtils::ConvertToInt64 ( XMP_StringPtr strValue )
{
if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue );
- errno = 0;
- char * numEnd;
+ int count;
+ char nextCh;
XMP_Int64 result;
- if ( strncmp ( strValue, "0x", 2 ) != 0 ) {
- result = strtol ( strValue, &numEnd, 0 );
+ if ( ! XMP_LitNMatch ( strValue, "0x", 2 ) ) {
+ count = sscanf ( strValue, "%lld%c", &result, &nextCh );
} else {
- result = (XMP_Int64) strtoul ( strValue, &numEnd, 0 ); // Treat hex form as raw bits.
+ count = sscanf ( strValue, "%llx%c", &result, &nextCh );
}
- if ( (errno != 0) || (*numEnd != 0) ) XMP_Throw ( "Invalid integer string", kXMPErr_BadParam );
+ if ( count != 1 ) XMP_Throw ( "Invalid integer string", kXMPErr_BadParam );
return result;
@@ -1276,7 +1276,8 @@ XMPUtils::ConvertToDate ( XMP_StringPtr strValue,
size_t pos = 0;
XMP_Int32 temp;
- (void) memset ( binValue, 0, sizeof ( XMP_DateTime ) );
+ XMP_Assert ( sizeof(*binValue) == sizeof(XMP_DateTime) );
+ (void) memset ( binValue, 0, sizeof(*binValue) ); // AUDIT: Safe, using sizeof destination.
bool timeOnly = ( (strValue[0] == 'T') ||
((strlen(strValue) >= 2) && (strValue[1] == ':')) ||
diff --git a/source/XMPCore/XMPUtils.hpp b/source/XMPCore/XMPUtils.hpp
index adba673..e1c7e78 100644
--- a/source/XMPCore/XMPUtils.hpp
+++ b/source/XMPCore/XMPUtils.hpp
@@ -14,6 +14,7 @@
#include "XMPMeta.hpp"
#include "XMPCore_Impl.hpp"
+#include "client-glue/WXMPUtils.hpp"
// -------------------------------------------------------------------------------------------------
diff --git a/source/XMPFiles/FileHandlers/ASF_Handler.cpp b/source/XMPFiles/FileHandlers/ASF_Handler.cpp
new file mode 100644
index 0000000..5726533
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/ASF_Handler.cpp
@@ -0,0 +1,362 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "ASF_Handler.hpp"
+
+// =================================================================================================
+/// \file ASF_Handler.hpp
+/// \brief File format handler for ASF.
+///
+/// This handler ...
+///
+// =================================================================================================
+
+// =================================================================================================
+// ASF_MetaHandlerCTor
+// ====================
+
+XMPFileHandler * ASF_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new ASF_MetaHandler ( parent );
+
+} // ASF_MetaHandlerCTor
+
+// =================================================================================================
+// ASF_CheckFormat
+// ===============
+
+bool ASF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles * parent )
+{
+
+ IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent);
+ XMP_Assert ( format == kXMP_WMAVFile );
+
+ IOBuffer ioBuf;
+
+ LFA_Seek ( fileRef, 0, SEEK_SET );
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, guidLen ) ) return false;
+
+ GUID guid;
+ memcpy ( &guid, ioBuf.ptr, guidLen );
+
+ if ( ! IsEqualGUID ( ASF_Header_Object, guid ) ) return false;
+
+ return true;
+
+} // ASF_CheckFormat
+
+// =================================================================================================
+// ASF_MetaHandler::ASF_MetaHandler
+// ==================================
+
+ASF_MetaHandler::ASF_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kASF_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+}
+
+// =================================================================================================
+// ASF_MetaHandler::~ASF_MetaHandler
+// ===================================
+
+ASF_MetaHandler::~ASF_MetaHandler()
+{
+ // Nothing extra to do.
+}
+
+// =================================================================================================
+// ASF_MetaHandler::CacheFileData
+// ===============================
+
+void ASF_MetaHandler::CacheFileData()
+{
+
+ this->containsXMP = false;
+
+ LFA_FileRef fileRef ( this->parent->fileRef );
+ if ( fileRef == 0 ) return;
+
+ ASF_Support support ( &this->legacyManager );
+ ASF_Support::ObjectState objectState;
+ long numTags = support.OpenASF ( fileRef, objectState );
+ if ( numTags == 0 ) return;
+
+ if ( objectState.xmpLen != 0 ) {
+
+ // XMP present
+
+ XMP_Int32 len = XMP_Int32 ( objectState.xmpLen );
+
+ this->xmpPacket.reserve( len );
+ this->xmpPacket.assign ( len, ' ' );
+
+ bool found = ASF_Support::ReadBuffer ( fileRef, objectState.xmpPos, objectState.xmpLen,
+ const_cast<char *>(this->xmpPacket.data()) );
+ if ( found ) {
+ this->packetInfo.offset = objectState.xmpPos;
+ this->packetInfo.length = len;
+ this->containsXMP = true;
+ }
+
+ }
+
+} // ASF_MetaHandler::CacheFileData
+
+// =================================================================================================
+// ASF_MetaHandler::ProcessTNail
+// ==============================
+
+void ASF_MetaHandler::ProcessTNail()
+{
+
+ XMP_Throw ( "ASF_MetaHandler::ProcessTNail isn't implemented yet", kXMPErr_Unimplemented );
+
+} // ASF_MetaHandler::ProcessTNail
+
+// =================================================================================================
+// ASF_MetaHandler::ProcessXMP
+// ============================
+//
+// Process the raw XMP and legacy metadata that was previously cached.
+
+void ASF_MetaHandler::ProcessXMP()
+{
+
+ this->processedXMP = true; // Make sure we only come through here once.
+
+ // Process the XMP packet.
+
+ if ( this->xmpPacket.empty() ) {
+
+ // import legacy in any case, when no XMP present
+ legacyManager.ImportLegacy ( &this->xmpObj );
+ this->legacyManager.SetDigest ( &this->xmpObj );
+
+ } else {
+
+ XMP_Assert ( this->containsXMP );
+ XMP_StringPtr packetStr = this->xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
+
+ this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
+
+ if ( ! legacyManager.CheckDigest ( this->xmpObj ) ) {
+ legacyManager.ImportLegacy ( &this->xmpObj );
+ }
+
+ }
+
+ // Assume we now have something in the XMP.
+ this->containsXMP = true;
+
+} // ASF_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// ASF_MetaHandler::UpdateFile
+// ============================
+
+void ASF_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+
+ bool updated = false;
+
+ if ( ! this->needsUpdate ) return;
+
+ LFA_FileRef fileRef ( this->parent->fileRef );
+ if ( fileRef == 0 ) return;
+
+ ASF_Support support;
+ ASF_Support::ObjectState objectState;
+ long numTags = support.OpenASF ( fileRef, objectState );
+ if ( numTags == 0 ) return;
+
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
+
+ this->legacyManager.ExportLegacy ( this->xmpObj );
+ if ( this->legacyManager.hasLegacyChanged() ) {
+
+ this->legacyManager.SetDigest ( &this->xmpObj );
+
+ // serialize with updated digest
+ if ( objectState.xmpLen == 0 ) {
+
+ // XMP does not exist, use standard padding
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+
+ } else {
+
+ // re-use padding with static XMP size
+ try {
+ XMP_OptionBits compactExact = (kXMP_UseCompactFormat | kXMP_ExactPacketLength);
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, compactExact, XMP_StringLen(objectState.xmpLen) );
+ } catch ( ... ) {
+ // re-use padding with exact packet length failed (legacy-digest needed too much space): try again using standard padding
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ }
+
+ }
+
+ }
+
+ XMP_StringPtr packetStr = xmpPacket.c_str();
+ packetLen = (XMP_StringLen)xmpPacket.size();
+ if ( packetLen == 0 ) return;
+
+ // value, when guessing for sufficient legacy padding (line-ending conversion etc.)
+ const int paddingTolerance = 50;
+
+ bool xmpGrows = ( objectState.xmpLen && (packetLen > objectState.xmpLen) && ( ! objectState.xmpIsLastObject) );
+
+ bool legacyGrows = ( this->legacyManager.hasLegacyChanged() &&
+ (this->legacyManager.getLegacyDiff() > (this->legacyManager.GetPadding() - paddingTolerance)) );
+
+ if ( doSafeUpdate || legacyGrows || xmpGrows ) {
+
+ // do a safe update in any case
+ updated = SafeWriteFile();
+
+ } else {
+
+ // possibly we can do an in-place update
+
+ if ( objectState.xmpLen < packetLen ) {
+
+ updated = SafeWriteFile();
+
+ } else {
+
+ // current XMP chunk size is sufficient -> write (in place update)
+ updated = ASF_Support::WriteBuffer(fileRef, objectState.xmpPos, packetLen, packetStr );
+
+ // legacy update
+ if ( updated && this->legacyManager.hasLegacyChanged() ) {
+
+ ASF_Support::ObjectIterator curPos = objectState.objects.begin();
+ ASF_Support::ObjectIterator endPos = objectState.objects.end();
+
+ for ( ; curPos != endPos; ++curPos ) {
+
+ ASF_Support::ObjectData object = *curPos;
+
+ // find header-object
+ if ( IsEqualGUID ( ASF_Header_Object, object.guid ) ) {
+ // update header object
+ updated = support.UpdateHeaderObject ( fileRef, object, legacyManager );
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( ! updated ) return; // If there's an error writing the chunk, bail.
+
+ this->needsUpdate = false;
+
+} // ASF_MetaHandler::UpdateFile
+
+// =================================================================================================
+// ASF_MetaHandler::WriteFile
+// ===========================
+
+void ASF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+ LFA_FileRef destRef = this->parent->fileRef;
+
+ ASF_Support support;
+ ASF_Support::ObjectState objectState;
+ long numTags = support.OpenASF ( sourceRef, objectState );
+ if ( numTags == 0 ) return;
+
+ LFA_Truncate ( destRef, 0 );
+
+ ASF_Support::ObjectIterator curPos = objectState.objects.begin();
+ ASF_Support::ObjectIterator endPos = objectState.objects.end();
+
+ for ( ; curPos != endPos; ++curPos ) {
+
+ ASF_Support::ObjectData object = *curPos;
+
+ // discard existing XMP object
+ if ( object.xmp ) continue;
+
+ // update header-object, when legacy needs update
+ if ( IsEqualGUID ( ASF_Header_Object, object.guid) && this->legacyManager.hasLegacyChanged( ) ) {
+ // rewrite header object
+ support.WriteHeaderObject ( sourceRef, destRef, object, this->legacyManager, false );
+ } else {
+ // copy any other object
+ ASF_Support::CopyObject ( sourceRef, destRef, object );
+ }
+
+ // write XMP object immediately after the (one and only) top-level DataObject
+ if ( IsEqualGUID ( ASF_Data_Object, object.guid ) ) {
+ XMP_StringPtr packetStr = xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
+ ASF_Support::WriteXMPObject ( destRef, packetLen, packetStr );
+ }
+
+ }
+
+ support.UpdateFileSize ( destRef );
+
+} // ASF_MetaHandler::WriteFile
+
+// =================================================================================================
+// ASF_MetaHandler::SafeWriteFile
+// ===========================
+
+bool ASF_MetaHandler::SafeWriteFile ()
+{
+ bool ret = false;
+
+ std::string origPath = this->parent->filePath;
+ LFA_FileRef origRef = this->parent->fileRef;
+
+ std::string updatePath;
+ LFA_FileRef updateRef = 0;
+
+ CreateTempFile ( origPath, &updatePath, kCopyMacRsrc );
+ updateRef = LFA_Open ( updatePath.c_str(), 'w' );
+
+ this->parent->filePath = updatePath;
+ this->parent->fileRef = updateRef;
+
+ try {
+ this->WriteFile ( origRef, origPath );
+ ret = true;
+ } catch ( ... ) {
+ LFA_Close ( updateRef );
+ this->parent->filePath = origPath;
+ this->parent->fileRef = origRef;
+ throw;
+ }
+
+ LFA_Close ( origRef );
+ LFA_Delete ( origPath.c_str() );
+
+ LFA_Close ( updateRef );
+ LFA_Rename ( updatePath.c_str(), origPath.c_str() );
+ this->parent->filePath = origPath;
+
+ this->parent->fileRef = 0;
+
+ return ret;
+
+} // ASF_MetaHandler::SafeWriteFile
+
+// =================================================================================================
diff --git a/source/XMPFiles/FileHandlers/ASF_Handler.hpp b/source/XMPFiles/FileHandlers/ASF_Handler.hpp
new file mode 100644
index 0000000..9f54b15
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/ASF_Handler.hpp
@@ -0,0 +1,66 @@
+#ifndef __ASF_Handler_hpp__
+#define __ASF_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2007 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMPFiles_Impl.hpp"
+#include "ASF_Support.hpp"
+
+// =================================================================================================
+/// \file ASF_Handler.hpp
+/// \brief File format handler for ASF.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// *** Could derive from Basic_Handler - buffer file tail in a temp file.
+
+extern XMPFileHandler* ASF_MetaHandlerCTor ( XMPFiles* parent );
+
+extern bool ASF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles* parent );
+
+static const XMP_OptionBits kASF_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_NeedsReadOnlyPacket
+ );
+
+class ASF_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessTNail();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteFile ( LFA_FileRef sourceRef, const std::string& sourcePath );
+
+ bool SafeWriteFile ();
+
+ ASF_MetaHandler ( XMPFiles* parent );
+ virtual ~ASF_MetaHandler();
+
+private:
+
+ ASF_LegacyManager legacyManager;
+
+}; // ASF_MetaHandler
+
+// =================================================================================================
+
+#endif /* __ASF_Handler_hpp__ */
diff --git a/source/XMPFiles/FileHandlers/AVCHD_Handler.cpp b/source/XMPFiles/FileHandlers/AVCHD_Handler.cpp
new file mode 100644
index 0000000..63b8f29
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/AVCHD_Handler.cpp
@@ -0,0 +1,648 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "AVCHD_Handler.hpp"
+
+#include "MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file AVCHD_Handler.cpp
+/// \brief Folder format handler for AVCHD.
+///
+/// This handler is for the AVCHD video format.
+///
+/// A typical AVCHD layout looks like:
+///
+/// BDMV/
+/// index.bdmv
+/// MovieObject.bdmv
+/// PLAYLIST/
+/// 00000.mpls
+/// 00001.mpls
+/// STREAM/
+/// 00000.m2ts
+/// 00001.m2ts
+/// CLIPINF/
+/// 00000.clpi
+/// 00001.clpi
+/// BACKUP/
+///
+// =================================================================================================
+
+// =================================================================================================
+
+// AVCHD Format. Book 1: Playback System Basic Specifications V 1.01. p. 76
+
+struct AVCHD_blkProgramInfo
+{
+ XMP_Uns32 mLength;
+ XMP_Uns8 mReserved1[2];
+ XMP_Uns32 mSPNProgramSequenceStart;
+ XMP_Uns16 mProgramMapPID;
+ XMP_Uns8 mNumberOfStreamsInPS;
+ XMP_Uns8 mReserved2;
+
+ // Video stream.
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mVideoFormat;
+ XMP_Uns8 mFrameRate;
+ XMP_Uns8 mAspectRatio;
+ XMP_Uns8 mCCFlag;
+ } mVideoStream;
+
+ // Audio stream.
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mAudioPresentationType;
+ XMP_Uns8 mSamplingFrequency;
+ XMP_Uns8 mAudioLanguageCode[4];
+ } mAudioStream;
+
+ // Pverlay bitmap stream.
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mOBLanguageCode[4];
+ } mOverlayBitmapStream;
+
+ // Menu bitmap stream.
+ struct
+ {
+ XMP_Uns8 mPresent;
+ XMP_Uns8 mBMLanguageCode[4];
+ } mMenuBitmapStream;
+
+};
+
+// =================================================================================================
+// AVCHD_CheckFormat
+// =================
+//
+// This version checks for the presence of a top level BPAV directory, and the required files and
+// directories immediately within it. The CLIPINF, PLAYLIST, and STREAM subfolders are required, as
+// are the index.bdmv and MovieObject.bdmv files.
+//
+// The state of the string parameters depends on the form of the path passed by the client. If the
+// client passed a logical clip path, like ".../MyMovie/00001", the parameters are:
+// rootPath - ".../MyMovie"
+// gpName - empty
+// parentName - empty
+// leafName - "00001"
+// If the client passed a full file path, like ".../MyMovie/BDMV/CLIPINF/00001.clpi", they are:
+// rootPath - ".../MyMovie"
+// gpName - "BDMV"
+// parentName - "CLIPINF" or "PALYLIST" or "STREAM"
+// leafName - "00001"
+
+// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has
+// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
+// ! file exists for a full file path.
+
+// ! Using explicit '/' as a separator when creating paths, it works on Windows.
+
+// ! Sample files show that the ".bdmv" extension can sometimes be ".bdm". Allow either.
+
+bool AVCHD_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty.
+
+ if ( ! gpName.empty() ) {
+ if ( gpName != "BDMV" ) return false;
+ if ( (parentName != "CLIPINF") && (parentName != "PLAYLIST") && (parentName != "STREAM") ) return false;
+ }
+
+ // Check the rest of the required general structure. Look for both ".bdmv" and ".bmd" extensions.
+
+ std::string bdmvPath ( rootPath );
+ bdmvPath += kDirChar;
+ bdmvPath += "BDMV";
+
+ if ( GetChildMode ( bdmvPath, "CLIPINF" ) != kFMode_IsFolder ) return false;
+ if ( GetChildMode ( bdmvPath, "PLAYLIST" ) != kFMode_IsFolder ) return false;
+ if ( GetChildMode ( bdmvPath, "STREAM" ) != kFMode_IsFolder ) return false;
+
+ if ( (GetChildMode ( bdmvPath, "index.bdmv" ) != kFMode_IsFile) &&
+ (GetChildMode ( bdmvPath, "index.bdm" ) != kFMode_IsFile) ) return false;
+
+ if ( (GetChildMode ( bdmvPath, "MovieObject.bdmv" ) != kFMode_IsFile) &&
+ (GetChildMode ( bdmvPath, "MovieObj.bdm" ) != kFMode_IsFile) ) return false;
+
+
+ // Make sure the .clpi file exists.
+ std::string tempPath ( bdmvPath );
+ tempPath += kDirChar;
+ tempPath += "CLIPINF";
+ tempPath += kDirChar;
+ tempPath += leafName;
+ tempPath += ".clpi";
+ const bool foundClpi = ( GetFileMode ( tempPath.c_str() ) == kFMode_IsFile );
+
+ // No .clpi -- check for .cpi instead.
+ if ( ! foundClpi ) {
+ tempPath.erase ( tempPath.size() - 5 ); // Remove the ".clpi" part.
+ tempPath += ".cpi";
+ if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false;
+ }
+
+ // And now save the pseudo path for the handler object.
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += leafName;
+ size_t pathLen = tempPath.size() + 1; // Include a terminating nul.
+ parent->handlerTemp = malloc ( pathLen );
+ if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for AVCHD clip info", kXMPErr_NoMemory );
+ memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen );
+
+ return true;
+
+} // AVCHD_CheckFormat
+
+// =================================================================================================
+// ReadAVCHDProgramInfo
+// ====================
+
+static bool ReadAVCHDProgramInfo ( const std::string& strPath, AVCHD_blkProgramInfo& avchdProgramInfo )
+{
+ try {
+
+ AutoFile idxFile;
+ idxFile.fileRef = LFA_Open ( strPath.c_str(), 'r' );
+ if ( idxFile.fileRef == 0 ) return false; // The open failed.
+
+ memset ( &avchdProgramInfo, 0, sizeof(AVCHD_blkProgramInfo) );
+
+ // Read clip header. (AVCHD Format. Book1. v. 1.01. p 64 )
+ struct AVCHD_ClipInfoHeader
+ {
+ XMP_Uns8 mTypeIndicator[4];
+ XMP_Uns8 mTypeIndicator2[4];
+ XMP_Uns32 mSequenceInfoStartAddress;
+ XMP_Uns32 mProgramInfoStartAddress;
+ XMP_Uns32 mCPIStartAddress;
+ XMP_Uns32 mExtensionDataStartAddress;
+ XMP_Uns8 mReserved[12];
+ };
+
+ // Read the AVCHD header.
+ AVCHD_ClipInfoHeader avchdHeader;
+ LFA_Read ( idxFile.fileRef, avchdHeader.mTypeIndicator, 4 );
+ LFA_Read ( idxFile.fileRef, avchdHeader.mTypeIndicator2, 4 );
+ avchdHeader.mSequenceInfoStartAddress = LFA_ReadUns32_BE ( idxFile.fileRef );
+ avchdHeader.mProgramInfoStartAddress = LFA_ReadUns32_BE ( idxFile.fileRef );
+ avchdHeader.mCPIStartAddress = LFA_ReadUns32_BE ( idxFile.fileRef );
+ avchdHeader.mExtensionDataStartAddress = LFA_ReadUns32_BE ( idxFile.fileRef );
+ LFA_Read ( idxFile.fileRef, avchdHeader.mReserved, 12 );
+
+ // Seek to the program header. (AVCHD Format. Book1. v. 1.01. p 77 )
+ LFA_Seek ( idxFile.fileRef, avchdHeader.mProgramInfoStartAddress, SEEK_SET );
+
+ avchdProgramInfo.mLength = LFA_ReadUns32_BE ( idxFile.fileRef );
+ LFA_Read ( idxFile.fileRef, avchdProgramInfo.mReserved1, 2 );
+ avchdProgramInfo.mSPNProgramSequenceStart = LFA_ReadUns32_BE ( idxFile.fileRef );
+ avchdProgramInfo.mProgramMapPID = LFA_ReadUns16_BE ( idxFile.fileRef );
+ LFA_Read ( idxFile.fileRef, &avchdProgramInfo.mNumberOfStreamsInPS, 1 );
+ LFA_Read ( idxFile.fileRef, &avchdProgramInfo.mReserved2, 1 );
+
+ XMP_Uns16 streamPID = 0;
+ for ( int i=0; i<avchdProgramInfo.mNumberOfStreamsInPS; ++i ) {
+
+ XMP_Uns8 length = 0;
+ XMP_Uns8 streamCodingType = 0;
+
+ streamPID = LFA_ReadUns16_BE ( idxFile.fileRef );
+ LFA_Read ( idxFile.fileRef, &length, 1 );
+
+ XMP_Int64 pos = LFA_Tell ( idxFile.fileRef );
+
+ LFA_Read ( idxFile.fileRef, &streamCodingType, 1 );
+
+ switch ( streamCodingType ) {
+
+ case 0x1B : // Video stream case.
+ {
+ XMP_Uns8 videoFormatAndFrameRate;
+ LFA_Read ( idxFile.fileRef, &videoFormatAndFrameRate, 1 );
+ avchdProgramInfo.mVideoStream.mVideoFormat = videoFormatAndFrameRate >> 4; // hi 4 bits
+ avchdProgramInfo.mVideoStream.mFrameRate = videoFormatAndFrameRate & 0x0f; // lo 4 bits
+
+ XMP_Uns8 aspectRatioAndReserved = 0;
+ LFA_Read ( idxFile.fileRef, &aspectRatioAndReserved, 1 );
+ avchdProgramInfo.mVideoStream.mAspectRatio = aspectRatioAndReserved >> 4; // hi 4 bits
+
+ XMP_Uns8 ccFlag = 0;
+ LFA_Read ( idxFile.fileRef, &ccFlag, 1 );
+ avchdProgramInfo.mVideoStream.mCCFlag = ccFlag;
+
+ avchdProgramInfo.mVideoStream.mPresent = 1;
+ }
+ break;
+
+ case 0x80 : // Fall through.
+ case 0x81 : // Audio stream case.
+ {
+ XMP_Uns8 audioPresentationTypeAndFrequency = 0;
+ LFA_Read ( idxFile.fileRef, &audioPresentationTypeAndFrequency, 1 );
+
+ avchdProgramInfo.mAudioStream.mAudioPresentationType = audioPresentationTypeAndFrequency >> 4; // hi 4 bits
+ avchdProgramInfo.mAudioStream.mSamplingFrequency = audioPresentationTypeAndFrequency & 0x0f; // lo 4 bits
+
+ LFA_Read ( idxFile.fileRef, avchdProgramInfo.mAudioStream.mAudioLanguageCode, 3 );
+ avchdProgramInfo.mAudioStream.mAudioLanguageCode[3] = 0;
+
+ avchdProgramInfo.mAudioStream.mPresent = 1;
+ }
+ break;
+
+ case 0x90 : // Overlay bitmap stream case.
+ LFA_Read ( idxFile.fileRef, &avchdProgramInfo.mOverlayBitmapStream.mOBLanguageCode, 3 );
+ avchdProgramInfo.mOverlayBitmapStream.mOBLanguageCode[3] = 0;
+ avchdProgramInfo.mOverlayBitmapStream.mPresent = 1;
+ break;
+
+ case 0x91 : // Menu bitmap stream.
+ LFA_Read ( idxFile.fileRef, &avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode, 3 );
+ avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode[3] = 0;
+ avchdProgramInfo.mMenuBitmapStream.mPresent = 1;
+ break;
+
+ default :
+ break;
+
+ }
+
+ LFA_Seek ( idxFile.fileRef, pos + length, SEEK_SET );
+
+ }
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ return true;
+
+} // ReadAVCHDProgramInfo
+
+// =================================================================================================
+// AVCHD_MetaHandlerCTor
+// =====================
+
+XMPFileHandler * AVCHD_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new AVCHD_MetaHandler ( parent );
+
+} // AVCHD_MetaHandlerCTor
+
+// =================================================================================================
+// AVCHD_MetaHandler::AVCHD_MetaHandler
+// ====================================
+
+AVCHD_MetaHandler::AVCHD_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kAVCHD_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ // Extract the root path and clip name.
+
+ XMP_Assert ( this->parent->handlerTemp != 0 );
+
+ this->rootPath.assign ( (char*) this->parent->handlerTemp );
+ free ( this->parent->handlerTemp );
+ this->parent->handlerTemp = 0;
+
+ SplitLeafName ( &this->rootPath, &this->clipName );
+
+} // AVCHD_MetaHandler::AVCHD_MetaHandler
+
+// =================================================================================================
+// AVCHD_MetaHandler::~AVCHD_MetaHandler
+// =====================================
+
+AVCHD_MetaHandler::~AVCHD_MetaHandler()
+{
+
+ if ( this->parent->handlerTemp != 0 ) {
+ free ( this->parent->handlerTemp );
+ this->parent->handlerTemp = 0;
+ }
+
+} // AVCHD_MetaHandler::~AVCHD_MetaHandler
+
+// =================================================================================================
+// AVCHD_MetaHandler::MakeClipInfoPath
+// ===================================
+
+void AVCHD_MetaHandler::MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix )
+{
+
+ *path = this->rootPath;
+ *path += kDirChar;
+ *path += "BDMV";
+ *path += kDirChar;
+ *path += "CLIPINF";
+ *path += kDirChar;
+ *path += this->clipName;
+ *path += suffix;
+
+} // AVCHD_MetaHandler::MakeClipInfoPath
+
+// =================================================================================================
+// AVCHD_MetaHandler::MakeClipStreamPath
+// =====================================
+
+void AVCHD_MetaHandler::MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix )
+{
+
+ *path = this->rootPath;
+ *path += kDirChar;
+ *path += "BDMV";
+ *path += kDirChar;
+ *path += "STREAM";
+ *path += kDirChar;
+ *path += this->clipName;
+ *path += suffix;
+
+} // AVCHD_MetaHandler::MakeClipStreamPath
+
+// =================================================================================================
+// AVCHD_MetaHandler::MakeLegacyDigest
+// ===================================
+
+#define kHexDigits "0123456789ABCDEF"
+
+void AVCHD_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ AVCHD_blkProgramInfo avchdProgramInfo;
+ std::string strPath;
+ this->MakeClipInfoPath ( &strPath, ".clpi" );
+
+ if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) {
+ this->MakeClipInfoPath ( &strPath, ".cpi" );
+ if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) return;
+ }
+
+ MD5_CTX context;
+ unsigned char digestBin [16];
+
+ MD5Init ( &context );
+ MD5Update ( &context, (XMP_Uns8*)&avchdProgramInfo, (unsigned int) sizeof(avchdProgramInfo) );
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->erase();
+ digestStr->append ( buffer, 32 );
+
+} // AVCHD_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// AVCHD_MetaHandler::CacheFileData
+// ================================
+
+void AVCHD_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) );
+
+ // See if the clip's .XMP file exists.
+
+ std::string xmpPath;
+ this->MakeClipStreamPath ( &xmpPath, ".xmp" );
+ if ( GetFileMode ( xmpPath.c_str() ) != kFMode_IsFile ) return; // No XMP.
+
+ // Read the entire .XMP file.
+
+ char openMode = 'r';
+ if ( this->parent->openFlags & kXMPFiles_OpenForUpdate ) openMode = 'w';
+
+ LFA_FileRef xmpFile = LFA_Open ( xmpPath.c_str(), openMode );
+ if ( xmpFile == 0 ) return; // The open failed.
+
+ XMP_Int64 xmpLen = LFA_Measure ( xmpFile );
+ if ( xmpLen > 100*1024*1024 ) {
+ XMP_Throw ( "AVCHD XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check.
+ }
+
+ this->xmpPacket.erase();
+ this->xmpPacket.reserve ( (size_t ) xmpLen );
+ this->xmpPacket.append ( (size_t ) xmpLen, ' ' );
+
+ XMP_Int32 ioCount = LFA_Read ( xmpFile, (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen, kLFA_RequireAll );
+ XMP_Assert ( ioCount == xmpLen );
+
+ this->packetInfo.offset = 0;
+ this->packetInfo.length = (XMP_Int32)xmpLen;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ XMP_Assert ( this->parent->fileRef == 0 );
+ if ( openMode == 'r' ) {
+ LFA_Close ( xmpFile );
+ } else {
+ this->parent->fileRef = xmpFile;
+ }
+
+ this->containsXMP = true;
+
+} // AVCHD_MetaHandler::CacheFileData
+
+// =================================================================================================
+// AVCHD_MetaHandler::ProcessXMP
+// =============================
+
+void AVCHD_MetaHandler::ProcessXMP()
+{
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ // read clip info
+ AVCHD_blkProgramInfo avchdProgramInfo;
+ std::string strPath;
+ this->MakeClipInfoPath ( &strPath, ".clpi" );
+
+ if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) {
+ this->MakeClipInfoPath ( &strPath, ".cpi" );
+ if ( ! ReadAVCHDProgramInfo ( strPath, avchdProgramInfo ) ) return;
+ }
+
+ // Video Stream. AVCHD Format v. 1.01 p. 78
+
+ XMP_StringPtr xmpValue = 0;
+
+ if ( avchdProgramInfo.mVideoStream.mPresent ) {
+
+ // XMP videoFrameRate.
+ xmpValue = 0;
+ switch ( avchdProgramInfo.mVideoStream.mFrameRate ) {
+ case 1 : xmpValue = "23.98p"; break; // "23.976"
+ case 2 : xmpValue = "24p"; break; // "24"
+ case 3 : xmpValue = "25p"; break; // "25"
+ case 4 : xmpValue = "29.97p"; break; // "29.97"
+ case 6 : xmpValue = "50i"; break; // "50"
+ case 7 : xmpValue = "59.94i"; break; // "59.94"
+ default: break;
+ }
+ if ( xmpValue != 0 ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "videoFrameRate", xmpValue, kXMP_DeleteExisting );
+ }
+
+ // XMP videoFrameSize.
+ xmpValue = 0;
+ int frameIndex = -1;
+ const char* frameWidth[4] = { "720", "720", "1280", "1920" };
+ const char* frameHeight[4] = { "480", "576", "720", "1080" };
+ switch ( avchdProgramInfo.mVideoStream.mVideoFormat ) {
+ case 1 : frameIndex = 0; break; // 480i
+ case 2 : frameIndex = 1; break; // 576i
+ case 3 : frameIndex = 0; break; // 480i
+ case 4 : frameIndex = 3; break; // 1080i
+ case 5 : frameIndex = 2; break; // 720p
+ case 6 : frameIndex = 3; break; // 1080p
+ default: break;
+ }
+ if ( frameIndex != -1 ) {
+ xmpValue = frameWidth[frameIndex];
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", xmpValue, 0 );
+ xmpValue = frameHeight[frameIndex];
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", xmpValue, 0 );
+ xmpValue = "pixels";
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", xmpValue, 0 );
+ }
+
+ this->containsXMP = true;
+
+ }
+
+ // Audio Stream.
+ if ( avchdProgramInfo.mAudioStream.mPresent ) {
+
+ xmpValue = 0;
+ switch ( avchdProgramInfo.mAudioStream.mAudioPresentationType ) {
+ case 1 : xmpValue = "Mono"; break;
+ case 3 : xmpValue = "Stereo"; break;
+ default : break;
+ }
+ if ( xmpValue != 0 ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "audioChannelType", xmpValue, kXMP_DeleteExisting );
+ }
+
+ xmpValue = 0;
+ switch ( avchdProgramInfo.mAudioStream.mSamplingFrequency ) {
+ case 1 : xmpValue = "48000"; break;
+ case 4 : xmpValue = "96000"; break;
+ case 5 : xmpValue = "192000"; break;
+ default : break;
+ }
+ if ( xmpValue != 0 ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "audioSampleRate", xmpValue, kXMP_DeleteExisting );
+ }
+
+ this->containsXMP = true;
+
+ }
+
+} // AVCHD_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// AVCHD_MetaHandler::UpdateFile
+// =============================
+//
+// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.
+
+void AVCHD_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "AVCHD", newDigest.c_str(), kXMP_DeleteExisting );
+
+ LFA_FileRef oldFile = this->parent->fileRef;
+
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );
+
+ if ( oldFile == 0 ) {
+
+ // The XMP does not exist yet.
+
+ std::string xmpPath;
+ this->MakeClipStreamPath ( &xmpPath, ".xmp" );
+
+ LFA_FileRef xmpFile = LFA_Create ( xmpPath.c_str() );
+ if ( xmpFile == 0 ) XMP_Throw ( "Failure creating AVCHD XMP file", kXMPErr_ExternalFailure );
+ LFA_Write ( xmpFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( xmpFile );
+
+ } else if ( ! doSafeUpdate ) {
+
+ // Over write the existing XMP file.
+
+ LFA_Seek ( oldFile, 0, SEEK_SET );
+ LFA_Truncate ( oldFile, 0 );
+ LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( oldFile );
+
+ } else {
+
+ // Do a safe update.
+
+ // *** We really need an LFA_SwapFiles utility.
+
+ std::string xmpPath, tempPath;
+
+ this->MakeClipStreamPath ( &xmpPath, ".xmp" );
+
+ CreateTempFile ( xmpPath, &tempPath );
+ LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' );
+ LFA_Write ( tempFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( tempFile );
+
+ LFA_Close ( oldFile );
+ LFA_Delete ( xmpPath.c_str() );
+ LFA_Rename ( tempPath.c_str(), xmpPath.c_str() );
+
+ }
+
+ this->parent->fileRef = 0;
+
+} // AVCHD_MetaHandler::UpdateFile
+
+// =================================================================================================
+// AVCHD_MetaHandler::WriteFile
+// ============================
+
+void AVCHD_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+
+ // ! WriteFile is not supposed to be called for handlers that own the file.
+ XMP_Throw ( "AVCHD_MetaHandler::WriteFile should not be called", kXMPErr_InternalFailure );
+
+} // AVCHD_MetaHandler::WriteFile
+
+// =================================================================================================
diff --git a/source/XMPFiles/FileHandlers/AVCHD_Handler.hpp b/source/XMPFiles/FileHandlers/AVCHD_Handler.hpp
new file mode 100644
index 0000000..45e9cf9
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/AVCHD_Handler.hpp
@@ -0,0 +1,77 @@
+#ifndef __AVCHD_Handler_hpp__
+#define __AVCHD_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles_Impl.hpp"
+
+#include "ExpatAdapter.hpp"
+
+// =================================================================================================
+/// \file AVCHD_Handler.hpp
+/// \brief Folder format handler for AVCHD.
+///
+/// This header ...
+///
+// =================================================================================================
+
+extern XMPFileHandler * AVCHD_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool AVCHD_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kAVCHD_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_FolderBasedFormat);
+
+class AVCHD_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files.
+ { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); };
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath );
+
+ AVCHD_MetaHandler ( XMPFiles * _parent );
+ virtual ~AVCHD_MetaHandler();
+
+private:
+
+ AVCHD_MetaHandler() {}; // Hidden on purpose.
+
+ void MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix );
+ void MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix );
+ void MakeLegacyDigest ( std::string * digestStr );
+
+ std::string rootPath, clipName;
+
+}; // AVCHD_MetaHandler
+
+// =================================================================================================
+
+#endif /* __AVCHD_Handler_hpp__ */
diff --git a/source/XMPFiles/FileHandlers/AVI_Handler.cpp b/source/XMPFiles/FileHandlers/AVI_Handler.cpp
index 6511246..96a11ec 100644
--- a/source/XMPFiles/FileHandlers/AVI_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/AVI_Handler.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -20,15 +20,10 @@
#endif
#endif
-
using namespace std;
#define kXMPUserDataType MakeFourCC ( '_', 'P', 'M', 'X' ) /* Yes, backwards! */
-
-/*******************************************************
-** Premiere Pro specific info for reconciliation
-*******************************************************/
// FourCC codes for the RIFF chunks
#define aviTimeChunk MakeFourCC('I','S','M','T')
#define avihdrlChunk MakeFourCC('h','d','r','l')
@@ -57,10 +52,6 @@ using namespace std;
#define kAltTapeName "altTapeName"
#define kLogComment "logComment"
-/*******************************************************
-*******************************************************/
-
-
// =================================================================================================
/// \file AVI_Handler.cpp
/// \brief File format handler for AVI.
@@ -144,14 +135,14 @@ void AVI_MetaHandler::UpdateFile ( bool doSafeUpdate )
if ( doSafeUpdate ) XMP_Throw ( "AVI_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
XMP_StringPtr packetStr = xmpPacket.c_str();
- XMP_StringLen packetLen = xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
if ( packetLen == 0 ) return;
// Make sure we're writing an even number of bytes as required by the RIFF specification.
if ( (xmpPacket.size() & 1) == 1 ) xmpPacket.push_back ( ' ' );
XMP_Assert ( (xmpPacket.size() & 1) == 0 );
packetStr = xmpPacket.c_str(); // ! Make sure they are current.
- packetLen = xmpPacket.size();
+ packetLen = (XMP_StringLen)xmpPacket.size();
LFA_FileRef fileRef(this->parent->fileRef);
if ( fileRef == 0 ) return;
@@ -161,7 +152,10 @@ void AVI_MetaHandler::UpdateFile ( bool doSafeUpdate )
if ( numTags == 0 ) return;
ok = RIFF_Support::PutChunk ( fileRef, riffState, formtypeAVI, kXMPUserDataType, (char*)packetStr, packetLen );
- if ( ! ok )return; // If there's an error writing the chunk, bail.
+ if ( ! ok ) return; // If there's an error writing the chunk, bail.
+
+ ok = CreatorAtom::Update ( this->xmpObj, fileRef, formtypeAVI, riffState );
+ if ( ! ok ) return;
// Update legacy metadata
@@ -185,7 +179,7 @@ void AVI_MetaHandler::UpdateFile ( bool doSafeUpdate )
ok = FindChunk ( riffState, myCommentChunk, myCommentList, 0, 0, 0, 0 );
- if ( ! ok ) {
+ if ( ok ) {
// Always rewrite the comment string, even if empty, so the user can erase it.
RIFF_Support::RewriteChunk ( fileRef, riffState, myCommentChunk, myCommentList, logCommentString.c_str() );
@@ -225,19 +219,29 @@ void AVI_MetaHandler::UpdateFile ( bool doSafeUpdate )
} else {
- ok = MakeChunk ( fileRef, riffState, formtypeAVI, PR_AVI_TIMELEN );
- if ( ! ok ) return; // If there's an error making a chunk, bail
-
- RIFF_Support::ltag listtag;
- listtag.id = MakeUns32LE ( FOURCC_LIST );
- listtag.len = MakeUns32LE ( PR_AVI_TIMELEN - 8 );
- listtag.subid = MakeUns32LE ( myTimeList );
- LFA_Write(fileRef, &listtag, 12);
-
- RIFF_Support::WriteChunk ( fileRef, myOrgTimeChunk, startTimecodeString.c_str(), TIMELEN );
- RIFF_Support::WriteChunk ( fileRef, myAltTimeChunk, altTimecodeString.c_str(), TIMELEN );
- RIFF_Support::WriteChunk ( fileRef, myOrgReelChunk, orgReelString.c_str(), REELLEN );
- RIFF_Support::WriteChunk ( fileRef, myAltReelChunk, altReelString.c_str(), REELLEN );
+ // We don't have the legacy part yet. If none of the XMP items exist then don't do anything.
+ // Otherwise, add all 4 even if empty. This is the original logic from way back.
+
+ bool haveAnyXMP = ( (! startTimecodeString.empty()) || (! altTimecodeString.empty()) ||
+ (! orgReelString.empty()) || (! altReelString.empty()) );
+
+ if ( haveAnyXMP ) {
+
+ ok = MakeChunk ( fileRef, riffState, formtypeAVI, PR_AVI_TIMELEN );
+ if ( ! ok ) return; // If there's an error making a chunk, bail
+
+ RIFF_Support::ltag listtag;
+ listtag.id = MakeUns32LE ( FOURCC_LIST );
+ listtag.len = MakeUns32LE ( PR_AVI_TIMELEN - 8 );
+ listtag.subid = MakeUns32LE ( myTimeList );
+ LFA_Write(fileRef, &listtag, 12);
+
+ RIFF_Support::WriteChunk ( fileRef, myOrgTimeChunk, startTimecodeString.c_str(), TIMELEN );
+ RIFF_Support::WriteChunk ( fileRef, myAltTimeChunk, altTimecodeString.c_str(), TIMELEN );
+ RIFF_Support::WriteChunk ( fileRef, myOrgReelChunk, orgReelString.c_str(), REELLEN );
+ RIFF_Support::WriteChunk ( fileRef, myAltReelChunk, altReelString.c_str(), REELLEN );
+
+ }
}
@@ -259,6 +263,24 @@ void AVI_MetaHandler::WriteFile ( LFA_FileRef sourceRef,
} // AVI_MetaHandler::WriteFile
// =================================================================================================
+
+static void StripSimpleEmpty ( SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr prop )
+{
+ // Small hack to clean up bad data. There are cases of code writing xmpDM:startTimecode and
+ // xmpDM:altTimecode as simple properties with empty values. They are supposed to be structs.
+
+ std::string value;
+ XMP_OptionBits flags;
+
+ bool found = xmp->GetProperty ( ns, prop, &value, &flags );
+
+ if ( found && XMP_PropIsSimple(flags) && value.empty() ) {
+ xmp->DeleteProperty ( ns, prop );
+ }
+
+}
+
+// =================================================================================================
// AVI_MetaHandler::CacheFileData
// ==============================
@@ -268,16 +290,48 @@ void AVI_MetaHandler::CacheFileData()
this->containsXMP = false;
- LFA_FileRef fileRef ( this->parent->fileRef );
+ LFA_FileRef fileRef ( this->parent->fileRef ); //*** simplify to assignment
if ( fileRef == 0 ) return;
+ bool updateFile = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate );
+ if ( updateFile ) {
+
+ // Workaround for bad files in the field that have a bad size in the outermost RIFF chunk.
+ // Repair the cases where the length is too long (beyond EOF). Don't repair a length that is
+ // less than EOF, we don't know if there actually are multiple top level chunks. There is
+ // also a check and "runtime repair" inside ReadTag, needed for read-only file access.
+
+ XMP_Int64 fileLen = LFA_Measure ( fileRef );
+ XMP_Uns32 riffLen;
+
+ LFA_Seek ( fileRef, 4, SEEK_SET );
+ LFA_Read ( fileRef, &riffLen, 4 );
+ riffLen = GetUns32LE ( &riffLen );
+
+ if ( (fileLen >= 8) && ((XMP_Int64)riffLen > (fileLen - 8)) ) { // Is the initial chunk too long?
+
+ bool repairFile = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenRepairFile );
+ if ( ! repairFile ) {
+ XMP_Throw ( "Initial RIFF tag exceeds file length", kXMPErr_BadValue );
+ } else {
+ riffLen = MakeUns32LE ( (XMP_Uns32)fileLen - 8 );
+ LFA_Seek ( fileRef, 4, SEEK_SET );
+ LFA_Write ( fileRef, &riffLen, 4 );
+ }
+
+ }
+
+ }
+
+ // Contnue with normal processing.
+
RIFF_Support::RiffState riffState;
long numTags = RIFF_Support::OpenRIFF ( fileRef, riffState );
- if ( numTags == 0 ) return;
+ if ( numTags == 0 ) return; //*** shouldn't we throw ? XMP_Throw("invalid file format") or such?
// Determine the size of the metadata
unsigned long bufferSize(0);
- ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType, 0, 0, 0, &bufferSize );
+ ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType /* _PMX, the xmp packet */, 0, 0, 0, &bufferSize);
if ( ! ok ) {
@@ -290,22 +344,26 @@ void AVI_MetaHandler::CacheFileData()
this->xmpPacket.assign ( bufferSize, ' ' );
// Get the metadata
- ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType, 0, 0, (char*)this->xmpPacket.c_str(), &bufferSize );
+ XMP_Uns64 xmpPacketPosition;
+ ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType /* _PMX, the xmp packet */, 0, 0,
+ (char*)this->xmpPacket.c_str(), &bufferSize, &xmpPacketPosition );
if ( ok ) {
- this->packetInfo.offset = kXMPFiles_UnknownOffset;
+ this->packetInfo.offset = xmpPacketPosition;
this->packetInfo.length = bufferSize;
- this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), this->xmpPacket.size() );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
this->containsXMP = true;
}
}
-
// Reconcile legacy metadata.
-
- std::string aviTimeString, orgTimeString, altTimeString;
+
+ std::string aviTimeString, orgTimeString, altTimeString, projectPathString;
unsigned long aviTimeSize, orgTimeSize, altTimeSize;
+ StripSimpleEmpty ( &this->xmpObj, kXMP_NS_DM, kStartTimecode );
+ StripSimpleEmpty ( &this->xmpObj, kXMP_NS_DM, kAltTimecode );
+
ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, aviTimeChunk, avihdrlChunk, 0, 0, &aviTimeSize );
if ( ok ) {
aviTimeString.reserve ( aviTimeSize );
@@ -422,11 +480,19 @@ void AVI_MetaHandler::CacheFileData()
}
- // Update the xmpPacket, as the xmpObj might have been updated with legacy info.
- this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
- this->packetInfo.offset = kXMPFiles_UnknownOffset;
- this->packetInfo.length = this->xmpPacket.size();
+ CreatorAtom::Import ( this->xmpObj, fileRef, riffState );
+
+ //// Update the xmpPacket, as the xmpObj might have been updated with legacy info.
+ //// Produce packet of same size [1781657]
+ //try {
+ // this->xmpObj.SerializeToBuffer ( &this->xmpPacket,
+ // (kXMP_UseCompactFormat | kXMP_ExactPacketLength) , packetInfo.length );
+ //} catch ( XMP_Error ) {
+ // this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat ) );
+ //}
+ // removed for [1781657] this->packetInfo.offset = kXMPFiles_UnknownOffset;
+ // removed for [1781657] this->packetInfo.length = (XMP_StringLen)this->xmpPacket.size();
this->processedXMP = this->containsXMP;
-
+
} // AVI_MetaHandler::CacheFileData
diff --git a/source/XMPFiles/FileHandlers/Basic_Handler.cpp b/source/XMPFiles/FileHandlers/Basic_Handler.cpp
index 845fe16..f3b471e 100644
--- a/source/XMPFiles/FileHandlers/Basic_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/Basic_Handler.cpp
@@ -71,7 +71,7 @@ void Basic_MetaHandler::UpdateFile ( bool doSafeUpdate )
LFA_Seek ( fileRef, 0, SEEK_END );
this->WriteXMPPrefix();
- LFA_Write ( fileRef, xmpPacket.c_str(), xmpPacket.size() );
+ LFA_Write ( fileRef, xmpPacket.c_str(), (XMP_StringLen)xmpPacket.size() );
this->WriteXMPSuffix();
if ( checkAbort && abortProc(abortArg) ) {
XMP_Throw ( "Basic_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort );
@@ -135,7 +135,7 @@ void Basic_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & s
// Write the new XMP section to the destination.
this->WriteXMPPrefix();
- LFA_Write ( destRef, this->xmpPacket.c_str(), this->xmpPacket.size() );
+ LFA_Write ( destRef, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
this->WriteXMPSuffix();
if ( checkAbort && abortProc(abortArg) ) {
XMP_Throw ( "Basic_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort );
diff --git a/source/XMPFiles/FileHandlers/Basic_Handler.hpp b/source/XMPFiles/FileHandlers/Basic_Handler.hpp
index 4f8dc77..45eef9f 100644
--- a/source/XMPFiles/FileHandlers/Basic_Handler.hpp
+++ b/source/XMPFiles/FileHandlers/Basic_Handler.hpp
@@ -57,6 +57,7 @@
static const XMP_OptionBits kBasic_HandlerFlags = (kXMPFiles_CanInjectXMP |
kXMPFiles_CanExpand |
kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
kXMPFiles_AllowsOnlyXMP |
kXMPFiles_ReturnsRawPacket |
kXMPFiles_AllowsSafeUpdate);
diff --git a/source/XMPFiles/FileHandlers/FLV_Handler.cpp b/source/XMPFiles/FileHandlers/FLV_Handler.cpp
new file mode 100644
index 0000000..2472870
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/FLV_Handler.cpp
@@ -0,0 +1,750 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "FLV_Handler.hpp"
+
+#include "MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file FLV_Handler.cpp
+/// \brief File format handler for FLV.
+///
+/// FLV is a fairly simple format, with a strong orientation to streaming use. It consists of a
+/// small file header then a sequence of tags that can contain audio data, video data, or
+/// ActionScript data. All integers in FLV are big endian.
+///
+/// For FLV version 1, the file header contains:
+///
+/// UI24 signature - the characters "FLV"
+/// UI8 version - 1
+/// UI8 flags - 0x01 = has video tags, 0x04 = has audio tags
+/// UI32 length in bytes of file header
+///
+/// For FLV version 1, each tag begins with an 11 byte header:
+///
+/// UI8 tag type - 8 = audio tag, 9 = video tag, 18 = script data tag
+/// UI24 content length in bytes
+/// UI24 time - low order 3 bytes
+/// UI8 time - high order byte
+/// UI24 stream ID
+///
+/// This is followed by the tag's content, then a UI32 "back pointer" which is the header size plus
+/// the content size. A UI32 zero is placed between the file header and the first tag as a
+/// terminator for backward scans. The time in a tag header is the start of playback for that tag.
+/// The tags must be in ascending time order. For a given time it is preferred that script data tags
+/// precede audio and video tags.
+///
+/// For metadata purposes only the script data tags are of interest. Script data information becomes
+/// accessible to ActionScript at the playback moment of the script data tag through a call to a
+/// registered data handler. The content of a script data tag contains a string and an ActionScript
+/// data value. The string is the name of the handler to be invoked, the data value is passed as an
+/// ActionScript Object parameter to the handler.
+///
+/// The XMP is placed in a script data tag with the name "onXMPData". A variety of legacy metadata
+/// is contained in a script data tag with the name "onMetaData". This contains only "internal"
+/// information (like duration or width/height), nothing that is user or author editiable (like
+/// title or description). Some of these legacy items are imported into the XMP, none are updated
+/// from the XMP.
+///
+/// A script data tag's content is:
+///
+/// UI8 0x02
+/// UI16 name length - includes nul terminator if present
+/// UI8n object name - UTF-8, possibly with nul terminator
+/// ... object value - serialized ActionScript value (SCRIPTDATAVALUE in FLV spec)
+///
+/// The onXMPData and onMetaData values are both ECMA arrays. These have more in common with XMP
+/// structs than arrays, the items have arbitrary string names. The serialized form is:
+///
+/// UI8 0x08
+/// UI32 array length - need not be exact, an optimization hint
+/// array items
+/// UI16 name length - includes nul terminator if present
+/// UI8n item name - UTF-8, possibly with nul terminator
+/// ... object value - serialized ActionScript value (SCRIPTDATAVALUE in FLV spec)
+/// UI24 0x000009 - array terminator
+///
+/// The object names and array item names in sample files do not have a nul terminator. The policy
+/// here is to treat them as optional when reading, and to omit them when writing.
+///
+/// The onXMPData array typically has one item named "liveXML". The value of this is a short or long
+/// string as necessary:
+///
+/// UI8 type - 2 for a short string, 12 for a long string
+/// UIx value length - UI16 for a short string, UI32 for a long string, includes nul terminator
+/// UI8n value - UTF-8 with nul terminator
+///
+// =================================================================================================
+
+static inline XMP_Uns32 GetUns24BE ( const void * addr )
+{
+ return (GetUns32BE(addr) >> 8);
+}
+
+static inline void PutUns24BE ( XMP_Uns32 value, void * addr )
+{
+ XMP_Uns8 * bytes = (XMP_Uns8*)addr;
+ bytes[0] = (XMP_Uns8)(value >> 16);
+ bytes[1] = (XMP_Uns8)(value >> 8);
+ bytes[2] = (XMP_Uns8)(value);
+}
+
+// =================================================================================================
+// FLV_CheckFormat
+// ===============
+//
+// Check for "FLV" and 1 in the first 4 bytes, that the header length is at least 9, that the file
+// size is at least as big as the header, and that the leading 0 back pointer is present if the file
+// is bigger than the header.
+
+#define kFLV1 0x464C5601UL
+
+bool FLV_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles * parent )
+{
+ XMP_Uns8 buffer [9];
+
+ LFA_Seek ( fileRef, 0, SEEK_SET );
+ XMP_Uns32 ioCount = LFA_Read ( fileRef, buffer, 9 );
+ if ( ioCount != 9 ) return false;
+
+ XMP_Uns32 fileSignature = GetUns32BE ( &buffer[0] );
+ if ( fileSignature != kFLV1 ) return false;
+
+ XMP_Uns32 headerSize = GetUns32BE ( &buffer[5] );
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+ if ( (fileSize < (headerSize + 4)) && (fileSize != headerSize) ) return false;
+
+ if ( fileSize >= (headerSize + 4) ) {
+ XMP_Uns32 bpZero;
+ LFA_Seek ( fileRef, headerSize, SEEK_SET );
+ ioCount = LFA_Read ( fileRef, &bpZero, 4 );
+ if ( (ioCount != 4) || (bpZero != 0) ) return false;
+ }
+
+ return true;
+
+} // FLV_CheckFormat
+
+// =================================================================================================
+// FLV_MetaHandlerCTor
+// ===================
+
+XMPFileHandler * FLV_MetaHandlerCTor ( XMPFiles * parent )
+{
+
+ return new FLV_MetaHandler ( parent );
+
+} // FLV_MetaHandlerCTor
+
+// =================================================================================================
+// FLV_MetaHandler::FLV_MetaHandler
+// ================================
+
+FLV_MetaHandler::FLV_MetaHandler ( XMPFiles * _parent )
+ : flvHeaderLen(0), longXMP(false), xmpTagPos(0), omdTagPos(0), xmpTagLen(0), omdTagLen(0)
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kFLV_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+} // FLV_MetaHandler::FLV_MetaHandler
+
+// =================================================================================================
+// FLV_MetaHandler::~FLV_MetaHandler
+// =================================
+
+FLV_MetaHandler::~FLV_MetaHandler()
+{
+
+ // Nothing to do yet.
+
+} // FLV_MetaHandler::~FLV_MetaHandler
+
+// =================================================================================================
+// GetTagInfo
+// ==========
+//
+// Seek to the start of a tag and extract the type, data size, and timestamp. Leave the file
+// positioned at the first byte of data.
+
+struct TagInfo {
+ XMP_Uns8 type;
+ XMP_Uns32 time;
+ XMP_Uns32 dataSize;
+};
+
+static void GetTagInfo ( LFA_FileRef fileRef, XMP_Uns64 tagPos, TagInfo * info )
+{
+ XMP_Uns8 buffer [11];
+
+ LFA_Seek ( fileRef, tagPos, SEEK_SET );
+ LFA_Read ( fileRef, buffer, 11, kLFA_RequireAll );
+
+ info->type = buffer[0];
+ info->time = GetUns24BE ( &buffer[4] ) || (buffer[7] << 24);
+ info->dataSize = GetUns24BE ( &buffer[1] );
+
+} // GetTagInfo
+
+// =================================================================================================
+// GetASValueLen
+// =============
+//
+// Return the full length of a serialized ActionScript value, including the type byte, zero if unknown.
+
+static XMP_Uns32 GetASValueLen ( const XMP_Uns8 * asValue, const XMP_Uns8 * asLimit )
+{
+ XMP_Uns32 valueLen = 0;
+ const XMP_Uns8 * itemPtr;
+ XMP_Uns32 arrayCount;
+
+ switch ( asValue[0] ) {
+
+ case 0 : // IEEE double
+ valueLen = 1 + 8;
+ break;
+
+ case 1 : // UI8 Boolean
+ valueLen = 1 + 1;
+ break;
+
+ case 2 : // Short string
+ valueLen = 1 + 2 + GetUns16BE ( &asValue[1] );
+ break;
+
+ case 3 : // ActionScript object, a name and value.
+ itemPtr = &asValue[1];
+ itemPtr += 2 + GetUns16BE ( itemPtr ); // Move past the name portion.
+ itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion.
+ valueLen = (XMP_Uns32) (itemPtr - asValue);
+ break;
+
+ case 4 : // Short string (movie clip path)
+ valueLen = 1 + 2 + GetUns16BE ( &asValue[1] );
+ break;
+
+ case 5 : // Null
+ valueLen = 1;
+ break;
+
+ case 6 : // Undefined
+ valueLen = 1;
+ break;
+
+ case 7 : // UI16 reference ID
+ valueLen = 1 + 2;
+ break;
+
+ case 8 : // ECMA array, ignore the count, look for the 0x000009 terminator.
+ itemPtr = &asValue[5];
+ while ( itemPtr < asLimit ) {
+ XMP_Uns16 nameLen = GetUns16BE ( itemPtr );
+ itemPtr += 2 + nameLen; // Move past the name portion.
+ if ( (nameLen == 0) && (*itemPtr == 9) ) {
+ itemPtr += 1;
+ break; // Done, found the 0x000009 terminator.
+ }
+ itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion.
+ }
+ valueLen = (XMP_Uns32) (itemPtr - asValue);
+ break;
+
+ case 10 : // Strict array, has an exact count.
+ arrayCount = GetUns32BE ( &asValue[1] );
+ itemPtr = &asValue[5];
+ for ( ; (arrayCount > 0) && (itemPtr < asLimit); --arrayCount ) {
+ itemPtr += 2 + GetUns16BE ( itemPtr ); // Move past the name portion.
+ itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion.
+ }
+ valueLen = (XMP_Uns32) (itemPtr - asValue);
+ break;
+
+ case 11 : // Date
+ valueLen = 1 + 8 + 2;
+ break;
+
+ case 12: // Long string
+ valueLen = 1 + 4 + GetUns32BE ( &asValue[1] );
+ break;
+
+ }
+
+ return valueLen;
+
+} // GetASValueLen
+
+// =================================================================================================
+// CheckName
+// =========
+//
+// Check for the name portion of a script data tag or array item, with optional nul terminator. The
+// wantedLen must not count the terminator.
+
+static inline bool CheckName ( XMP_StringPtr inputName, XMP_Uns16 inputLen,
+ XMP_StringPtr wantedName, XMP_Uns16 wantedLen )
+{
+
+ if ( inputLen == wantedLen+1 ) {
+ if ( inputName[wantedLen] != 0 ) return false; // Extra byte must be terminating nul.
+ --inputLen;
+ }
+
+ if ( (inputLen == wantedLen) && XMP_LitNMatch ( inputName, wantedName, wantedLen ) ) return true;
+ return false;
+
+} // CheckName
+
+// =================================================================================================
+// FLV_MetaHandler::CacheFileData
+// ==============================
+//
+// Look for the onXMPData and onMetaData script data tags at time 0. Cache all of onMetaData, it
+// shouldn't be that big and this removes a need to know what is reconciled. It can't be more than
+// 16MB anyway, the size field is only 24 bits.
+
+void FLV_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) );
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ LFA_FileRef fileRef = this->parent->fileRef;
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+
+ XMP_Uns8 buffer [16]; // Enough for 1+2+"onMetaData"+nul.
+ XMP_Uns32 ioCount;
+ TagInfo info;
+
+ LFA_Seek ( fileRef, 5, SEEK_SET );
+ LFA_Read ( fileRef, buffer, 4, kLFA_RequireAll );
+
+ this->flvHeaderLen = GetUns32BE ( &buffer[0] );
+ XMP_Uns32 firstTagPos = this->flvHeaderLen + 4; // Include the initial zero back pointer.
+
+ if ( firstTagPos >= fileSize ) return; // Quit now if the file is just a header.
+
+ for ( XMP_Uns64 tagPos = firstTagPos; tagPos < fileSize; tagPos += (11 + info.dataSize + 4) ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "FLV_MetaHandler::LookForMetadata - User abort", kXMPErr_UserAbort );
+ }
+
+ GetTagInfo ( fileRef, tagPos, &info ); // ! GetTagInfo seeks to the tag offset.
+ if ( info.time != 0 ) break;
+ if ( info.type != 18 ) continue;
+
+ XMP_Assert ( sizeof(buffer) >= (1+2+10+1) ); // 02 000B onMetaData 00
+ ioCount = LFA_Read ( fileRef, buffer, sizeof(buffer) );
+ if ( (ioCount < 4) || (buffer[0] != 0x02) ) continue;
+
+ XMP_Uns16 nameLen = GetUns16BE ( &buffer[1] );
+ XMP_StringPtr namePtr = (XMP_StringPtr)(&buffer[3]);
+
+ if ( this->onXMP.empty() && CheckName ( namePtr, nameLen, "onXMPData", 9 ) ) {
+
+ // ! Put the raw data in onXMPData, analyze the value in ProcessXMP.
+
+ this->xmpTagPos = tagPos;
+ this->xmpTagLen = 11 + info.dataSize + 4; // ! Includes the trailing back pointer.
+
+ this->packetInfo.offset = tagPos + 11 + 1+2+nameLen; // ! Not the real offset yet, the offset of the onXMPData value.
+
+ ioCount = info.dataSize - (1+2+nameLen); // Just the onXMPData value portion.
+ this->onXMP.reserve ( ioCount );
+ this->onXMP.assign ( ioCount, ' ' );
+ LFA_Seek ( fileRef, this->packetInfo.offset, SEEK_SET );
+ LFA_Read ( fileRef, (void*)this->onXMP.data(), ioCount, kLFA_RequireAll );
+
+ if ( ! this->onMetaData.empty() ) break; // Done if we've found both.
+
+ } else if ( this->onMetaData.empty() && CheckName ( namePtr, nameLen, "onMetaData", 10 ) ) {
+
+ this->omdTagPos = tagPos;
+ this->omdTagLen = 11 + info.dataSize + 4; // ! Includes the trailing back pointer.
+
+ ioCount = info.dataSize - (1+2+nameLen); // Just the onMetaData value portion.
+ this->onMetaData.reserve ( ioCount );
+ this->onMetaData.assign ( ioCount, ' ' );
+ LFA_Seek ( fileRef, (tagPos + 11 + 1+2+nameLen), SEEK_SET );
+ LFA_Read ( fileRef, (void*)this->onMetaData.data(), ioCount, kLFA_RequireAll );
+
+ if ( ! this->onXMP.empty() ) break; // Done if we've found both.
+
+ }
+
+ }
+
+} // FLV_MetaHandler::CacheFileData
+
+// =================================================================================================
+// FLV_MetaHandler::MakeLegacyDigest
+// =================================
+
+#define kHexDigits "0123456789ABCDEF"
+
+void FLV_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ MD5_CTX context;
+ unsigned char digestBin [16];
+
+ MD5Init ( &context );
+ MD5Update ( &context, (XMP_Uns8*)this->onMetaData.data(), (unsigned int)this->onMetaData.size() );
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->erase();
+ digestStr->append ( buffer, 32 );
+
+} // FLV_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// FLV_MetaHandler::ExtractLiveXML
+// ===============================
+//
+// Extract the XMP packet from the cached onXMPData ECMA array's "liveXMP" item.
+
+void FLV_MetaHandler::ExtractLiveXML()
+{
+ if ( this->onXMP[0] != 0x08 ) return; // Make sure onXMPData is an ECMA array.
+ const XMP_Uns8 * ecmaArray = (const XMP_Uns8 *) this->onXMP.c_str();
+ const XMP_Uns8 * ecmaLimit = ecmaArray + this->onXMP.size();
+
+ if ( this->onXMP.size() >= 3 ) { // Omit the 0x000009 terminator, simplifies the loop.
+ if ( GetUns24BE ( ecmaLimit-3 ) == 9 ) ecmaLimit -= 3;
+ }
+
+ for ( const XMP_Uns8 * itemPtr = ecmaArray + 5; itemPtr < ecmaLimit; /* internal increment */ ) {
+
+ // Find the "liveXML" array item, make sure it is a short or long string.
+
+ XMP_Uns16 nameLen = GetUns16BE ( itemPtr );
+ const XMP_Uns8 * namePtr = itemPtr + 2;
+
+ itemPtr += (2 + nameLen); // Move to the value portion.
+ XMP_Uns32 valueLen = GetASValueLen ( itemPtr, ecmaLimit );
+ if ( valueLen == 0 ) return; // ! Unknown value type, can't look further.
+
+ if ( CheckName ( (char*)namePtr, nameLen, "liveXML", 7 ) ) {
+
+ XMP_Uns32 lenLen = 2; // Assume a short string.
+ if ( *itemPtr == 12 ) {
+ lenLen = 4;
+ this->longXMP = true;
+ } else if ( *itemPtr != 2 ) {
+ return; // Not a short or long string.
+ }
+
+ valueLen -= (1 + lenLen);
+ itemPtr += (1 + lenLen);
+
+ this->packetInfo.offset += (itemPtr - ecmaArray);
+ this->packetInfo.length += valueLen;
+
+ this->xmpPacket.reserve ( valueLen );
+ this->xmpPacket.assign ( (char*)itemPtr, valueLen );
+
+ return;
+
+ }
+
+ itemPtr += valueLen; // Move past the value portion.
+
+ }
+
+} // FLV_MetaHandler::ExtractLiveXML
+
+// =================================================================================================
+// FLV_MetaHandler::ProcessXMP
+// ===========================
+
+void FLV_MetaHandler::ProcessXMP()
+{
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( ! this->onXMP.empty() ) { // Look for the XMP packet.
+
+ this->ExtractLiveXML();
+ if ( ! this->xmpPacket.empty() ) {
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->containsXMP = true;
+ }
+
+ }
+
+ // Now process the legacy, if necessary.
+
+ if ( this->onMetaData.empty() ) return; // No legacy, we're done.
+
+ std::string oldDigest;
+ bool oldDigestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "FLV", &oldDigest, 0 );
+
+ if ( oldDigestFound ) {
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) return; // No legacy changes.
+ }
+
+ // *** No spec yet for what legacy to reconcile.
+
+} // FLV_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// FLV_MetaHandler::UpdateFile
+// ===========================
+
+void FLV_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates.
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ LFA_FileRef fileRef = this->parent->fileRef;
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+
+ // Make sure the XMP has a legacy digest if appropriate.
+
+ if ( ! this->onMetaData.empty() ) {
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests",
+ kXMP_NS_XMP, "FLV", newDigest.c_str(), kXMP_DeleteExisting );
+
+ try {
+ XMP_StringLen xmpLen = (XMP_StringLen)this->xmpPacket.size();
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength), xmpLen );
+ } catch ( ... ) {
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ }
+
+ }
+
+ // Rewrite the packet in-place if it fits. Otherwise rewrite the whole file.
+
+ if ( this->xmpPacket.size() == (size_t)this->packetInfo.length ) {
+
+ LFA_Seek ( fileRef, this->packetInfo.offset, SEEK_SET );
+ LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() );
+
+ } else {
+
+ std::string origPath = this->parent->filePath;
+ LFA_FileRef origRef = this->parent->fileRef;
+
+ std::string updatePath;
+ LFA_FileRef updateRef = 0;
+
+ CreateTempFile ( origPath, &updatePath );
+ updateRef = LFA_Open ( updatePath.c_str(), 'w' );
+
+ this->parent->filePath = updatePath;
+ this->parent->fileRef = updateRef;
+
+ try {
+ this->WriteFile ( origRef, origPath );
+ } catch ( ... ) {
+ LFA_Close ( updateRef );
+ this->parent->filePath = origPath;
+ this->parent->fileRef = origRef;
+ throw;
+ }
+
+ LFA_Close ( origRef );
+ LFA_Delete ( origPath.c_str() );
+
+ LFA_Close ( updateRef );
+ LFA_Rename ( updatePath.c_str(), origPath.c_str() );
+ this->parent->filePath = origPath;
+ this->parent->fileRef = 0;
+
+ }
+
+ this->needsUpdate = false;
+
+} // FLV_MetaHandler::UpdateFile
+
+// =================================================================================================
+// WriteOnXMP
+// ==========
+//
+// Write the XMP packet wrapped up in an ECMA array script data tag:
+//
+// 0 UI8 tag type : 18
+// 1 UI24 content length : 1+2+9+1+4+2+7+1 + <2 or 4> + XMP packet size + 1 + 3
+// 4 UI24 time low : 0
+// 7 UI8 time high : 0
+// 8 UI24 stream ID : 0
+//
+// 11 UI8 0x02
+// 12 UI16 name length : 9
+// 14 str9 tag name : "onXMPData", no nul terminator
+// 23 UI8 value type : 8
+// 24 UI32 array count : 1
+// 28 UI16 name length : 7
+// 30 str7 item name : "liveXML", no nul terminator
+//
+// 37 UI8 value type : 2 for a short string, 12 for a long string
+// 38 UIn XMP packet size + 1, UI16 or UI32 as needed
+// -- str XMP packet, with nul terminator
+//
+// -- UI24 array terminator : 0x000009
+// -- UI32 back pointer : content length + 11
+
+static void WriteOnXMP ( LFA_FileRef fileRef, const std::string & xmpPacket )
+{
+ char buffer [64];
+ bool longXMP = false;
+ XMP_Uns32 tagLen = 1+2+9+1+4+2+7+1 + 2 + (XMP_Uns32)xmpPacket.size() + 1 + 3;
+
+ if ( xmpPacket.size() > 0xFFFE ) {
+ longXMP = true;
+ tagLen += 2;
+ }
+
+ if ( tagLen > 16*1024*1024 ) XMP_Throw ( "FLV tags can't be larger than 16MB", kXMPErr_TBD );
+
+ // Fill in the script data tag header.
+
+ buffer[0] = 18;
+ PutUns24BE ( tagLen, &buffer[1] );
+ PutUns24BE ( 0, &buffer[4] );
+ buffer[7] = 0;
+ PutUns24BE ( 0, &buffer[8] );
+
+ // Fill in the "onXMPData" name, ECMA array start, and "liveXML" name.
+
+ buffer[11] = 2;
+ PutUns16BE ( 9, &buffer[12] );
+ memcpy ( &buffer[14], "onXMPData", 9 ); // AUDIT: Safe, buffer has 64 chars.
+ buffer[23] = 8;
+ PutUns32BE ( 1, &buffer[24] );
+ PutUns16BE ( 7, &buffer[28] );
+ memcpy ( &buffer[30], "liveXML", 7 ); // AUDIT: Safe, buffer has 64 chars.
+
+ // Fill in the XMP packet string type and length, write what we have so far.
+
+ LFA_Seek ( fileRef, 0, SEEK_END );
+ if ( ! longXMP ) {
+ buffer[37] = 2;
+ PutUns16BE ( (XMP_Uns16)xmpPacket.size()+1, &buffer[38] );
+ LFA_Write ( fileRef, buffer, 40 );
+ } else {
+ buffer[37] = 12;
+ PutUns32BE ( (XMP_Uns32)xmpPacket.size()+1, &buffer[38] );
+ LFA_Write ( fileRef, buffer, 42 );
+ }
+
+ // Write the XMP packet, nul terminator, array terminator, and back pointer.
+
+ LFA_Write ( fileRef, xmpPacket.c_str(), (XMP_Int32)xmpPacket.size()+1 );
+ PutUns24BE ( 9, &buffer[0] );
+ PutUns32BE ( tagLen+11, &buffer[3] );
+ LFA_Write ( fileRef, buffer, 7 );
+
+} // WriteOnXMP
+
+// =================================================================================================
+// FLV_MetaHandler::WriteFile
+// ==========================
+//
+// Use a source (old) file and the current XMP to build a destination (new) file. All of the source
+// file is copied except for previous XMP. The current XMP is inserted after onMetaData, or at least
+// before the first time 0 audio or video tag.
+
+// ! We do not currently update anything in onMetaData.
+
+void FLV_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+ if ( ! this->needsUpdate ) return;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ LFA_FileRef destRef = this->parent->fileRef;
+
+ XMP_Uns64 sourceLen = LFA_Measure ( sourceRef );
+ XMP_Uns64 sourcePos = 0;
+
+ LFA_Seek ( sourceRef, 0, SEEK_SET );
+ LFA_Seek ( destRef, 0, SEEK_SET );
+ LFA_Truncate ( destRef, 0 );
+
+ // First do whatever is needed to put the new XMP after any existing onMetaData tag, or as the
+ // first time 0 tag.
+
+ if ( this->omdTagPos == 0 ) {
+
+ // There is no onMetaData tag. Copy the file header, then write the new XMP as the first tag.
+ // Allow the degenerate case of a file with just a header, no initial back pointer or tags.
+
+ LFA_Copy ( sourceRef, destRef, this->flvHeaderLen, abortProc, abortArg );
+
+ XMP_Uns32 zero = 0; // Don't require the initial 0 back pointer to be in the source file.
+ LFA_Write ( destRef, &zero, 4 );
+
+ sourcePos = this->flvHeaderLen + 4;
+
+ WriteOnXMP ( destRef, this->xmpPacket );
+
+ } else {
+
+ // There is an onMetaData tag. Copy the front of the file through the onMetaData tag,
+ // skipping any XMP that happens to be in the way. The XMP should not be before onMetaData,
+ // but let's be robust. Write the new XMP immediately after onMetaData, at the same time.
+
+ XMP_Uns64 omdEnd = this->omdTagPos + this->omdTagLen;
+
+ if ( (this->xmpTagPos != 0) && (this->xmpTagPos < this->omdTagPos) ) {
+ LFA_Copy ( sourceRef, destRef, this->xmpTagPos, abortProc, abortArg );
+ sourcePos = this->xmpTagPos + this->xmpTagLen;
+ LFA_Seek ( sourceRef, sourcePos, SEEK_SET );
+ }
+
+ LFA_Copy ( sourceRef, destRef, (omdEnd - sourcePos), abortProc, abortArg );
+ sourcePos = omdEnd;
+
+ WriteOnXMP ( destRef, this->xmpPacket );
+
+ }
+
+ // Copy the rest of the file, skipping any XMP that is in the way.
+
+ if ( (this->xmpTagPos != 0) && (this->xmpTagPos >= sourcePos) ) {
+ LFA_Copy ( sourceRef, destRef, (this->xmpTagPos - sourcePos), abortProc, abortArg );
+ sourcePos = this->xmpTagPos + this->xmpTagLen;
+ LFA_Seek ( sourceRef, sourcePos, SEEK_SET );
+ }
+
+ LFA_Copy ( sourceRef, destRef, (sourceLen - sourcePos), abortProc, abortArg );
+
+ this->needsUpdate = false;
+
+} // FLV_MetaHandler::WriteFile
+
+// =================================================================================================
diff --git a/source/XMPFiles/FileHandlers/FLV_Handler.hpp b/source/XMPFiles/FileHandlers/FLV_Handler.hpp
new file mode 100644
index 0000000..1fea76d
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/FLV_Handler.hpp
@@ -0,0 +1,73 @@
+#ifndef __FLV_Handler_hpp__
+#define __FLV_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2007 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMPFiles_Impl.hpp"
+
+// ================================================================================================
+/// \file FLV_Handler.hpp
+/// \brief File format handler for FLV.
+///
+/// This header ...
+///
+// ================================================================================================
+
+extern XMPFileHandler * FLV_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool FLV_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kFLV_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_AllowsSafeUpdate
+ );
+
+class FLV_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath );
+
+ FLV_MetaHandler ( XMPFiles * _parent );
+ virtual ~FLV_MetaHandler();
+
+private:
+
+ FLV_MetaHandler() : flvHeaderLen(0), longXMP(false),
+ xmpTagPos(0), omdTagPos(0), xmpTagLen(0), omdTagLen(0) {}; // Hidden on purpose.
+
+ void ExtractLiveXML();
+ void MakeLegacyDigest ( std::string * digestStr );
+
+ XMP_Uns32 flvHeaderLen;
+ bool longXMP; // True if the stored XMP is a long string (4 byte length).
+
+ XMP_Uns64 xmpTagPos, omdTagPos; // The file offset and length of onXMP and onMetaData tags.
+ XMP_Uns32 xmpTagLen, omdTagLen; // Zero if the tag is not present.
+
+ std::string onXMP, onMetaData; // ! Actually contains structured binary data.
+
+}; // FLV_MetaHandler
+
+// =================================================================================================
+
+#endif // __FLV_Handler_hpp__
diff --git a/source/XMPFiles/FileHandlers/InDesign_Handler.cpp b/source/XMPFiles/FileHandlers/InDesign_Handler.cpp
index 55332c0..c26db20 100644
--- a/source/XMPFiles/FileHandlers/InDesign_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/InDesign_Handler.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -208,14 +208,15 @@ void InDesign_MetaHandler::CacheFileData()
XMP_Assert ( ! this->streamBigEndian );
if ( cobjEndian == kINDD_BigEndian ) this->streamBigEndian = true;
-
+
// ---------------------------------------------------------------------------------------------
// Look for the XMP contiguous object stream. Most of the time there will be just one stream and
// it will be the XMP. So we might as well fill the whole buffer and not worry about reading too
// much and seeking back to the start of the following stream.
XMP_Int64 cobjPos = (XMP_Int64)dbPages * kINDD_PageSize; // ! Use a 64 bit multiply!
- XMP_Uns32 streamLength = (XMP_Uns32)(-(long)(2*sizeof(InDesignContigObjMarker))); // ! For the first pass in the loop.
+ cobjPos -= (2 * sizeof(InDesignContigObjMarker)); // ! For the first pass in the loop.
+ XMP_Uns32 streamLength = 0; // ! For the first pass in the loop.
while ( true ) {
@@ -225,7 +226,7 @@ void InDesign_MetaHandler::CacheFileData()
// Fetch the start of the next stream and check the contiguous object header.
// ! The writeable bit of fObjectClassID is ignored, we use the packet trailer flag.
-
+
cobjPos += streamLength + (2 * sizeof(InDesignContigObjMarker));
ioBuf.filePos = cobjPos;
ioBuf.ptr = ioBuf.limit; // Make sure RefillBuffer does a simple read.
@@ -331,18 +332,18 @@ void InDesign_MetaHandler::WriteXMPPrefix()
// Write the contiguous object header and the 4 byte length of the XMP packet.
LFA_FileRef fileRef = this->parent->fileRef;
- XMP_PacketInfo & packetInfo = this->packetInfo;
+ XMP_Uns32 packetSize = (XMP_Uns32)this->xmpPacket.size();
InDesignContigObjMarker header;
memcpy ( header.fGUID, kINDDContigObjHeaderGUID, sizeof(header.fGUID) ); // AUDIT: Use of dest sizeof for length is safe.
header.fObjectUID = this->xmpObjID;
header.fObjectClassID = this->xmpClassID;
- header.fStreamLength = MakeUns32LE ( 4 + packetInfo.length );
+ header.fStreamLength = MakeUns32LE ( 4 + packetSize );
header.fChecksum = (XMP_Uns32)(-1);
LFA_Write ( fileRef, &header, sizeof(header) );
- XMP_Uns32 pktLength = MakeUns32LE ( packetInfo.length );
- if ( this->streamBigEndian ) pktLength = MakeUns32BE ( packetInfo.length );
+ XMP_Uns32 pktLength = MakeUns32LE ( packetSize );
+ if ( this->streamBigEndian ) pktLength = MakeUns32BE ( packetSize );
LFA_Write ( fileRef, &pktLength, sizeof(pktLength) );
} // InDesign_MetaHandler::WriteXMPPrefix
@@ -356,14 +357,14 @@ void InDesign_MetaHandler::WriteXMPSuffix()
// Write the contiguous object trailer.
LFA_FileRef fileRef = this->parent->fileRef;
- XMP_PacketInfo & packetInfo = this->packetInfo;
+ XMP_Uns32 packetSize = (XMP_Uns32)this->xmpPacket.size();
InDesignContigObjMarker trailer;
memcpy ( trailer.fGUID, kINDDContigObjTrailerGUID, sizeof(trailer.fGUID) ); // AUDIT: Use of dest sizeof for length is safe.
trailer.fObjectUID = this->xmpObjID;
trailer.fObjectClassID = this->xmpClassID;
- trailer.fStreamLength = MakeUns32LE ( 4 + packetInfo.length );
+ trailer.fStreamLength = MakeUns32LE ( 4 + packetSize );
trailer.fChecksum = (XMP_Uns32)(-1);
LFA_Write ( fileRef, &trailer, sizeof(trailer) );
@@ -409,7 +410,6 @@ void InDesign_MetaHandler::RestoreFileEnding()
// Pad the file with zeros to a page boundary.
LFA_FileRef fileRef = this->parent->fileRef;
- XMP_PacketInfo & packetInfo = this->packetInfo;
XMP_Int64 dataLength = LFA_Measure ( fileRef );
XMP_Int32 padLength = (kINDD_PageSize - ((XMP_Int32)dataLength & kINDD_PageMask)) & kINDD_PageMask;
diff --git a/source/XMPFiles/FileHandlers/JPEG_Handler.cpp b/source/XMPFiles/FileHandlers/JPEG_Handler.cpp
index e57c83f..6917355 100644
--- a/source/XMPFiles/FileHandlers/JPEG_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/JPEG_Handler.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -352,7 +352,7 @@ void JPEG_MetaHandler::CacheFileData()
if ( ! ok ) return; // Must be a truncated file.
this->packetInfo.offset = ioBuf.filePos + (ioBuf.ptr - &ioBuf.data[0]);
- this->packetInfo.length = segLen;
+ this->packetInfo.length = (XMP_Int32)segLen;
this->packetInfo.padSize = 0; // Assume for now, set these properly in ProcessXMP.
this->packetInfo.charForm = kXMP_CharUnknown;
this->packetInfo.writeable = true;
@@ -544,7 +544,7 @@ void JPEG_MetaHandler::ProcessTNail()
} else {
this->exifMgr = new TIFF_FileWriter();
}
- this->exifMgr->ParseMemoryStream ( this->exifContents.c_str(), this->exifContents.size() );
+ this->exifMgr->ParseMemoryStream ( this->exifContents.c_str(), (XMP_Uns32)this->exifContents.size() );
}
this->containsTNail = this->exifMgr->GetTNailInfo ( &this->tnailInfo );
@@ -577,7 +577,12 @@ void JPEG_MetaHandler::ProcessXMP()
} else {
if ( this->exifMgr == 0 ) this->exifMgr = new TIFF_FileWriter();
this->psirMgr = new PSIR_FileWriter();
- this->iptcMgr = new IPTC_Writer(); // ! Parse it later.
+ #if ! XMP_UNIXBuild
+ this->iptcMgr = new IPTC_Writer(); // ! Parse it later.
+ #else
+ // ! Hack until the legacy-as-local issues are resolved for generic UNIX.
+ this->iptcMgr = new IPTC_Reader(); // ! Import IPTC but don't export it.
+ #endif
}
// Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy
@@ -594,11 +599,11 @@ void JPEG_MetaHandler::ProcessXMP()
IPTC_Manager & iptc = *this->iptcMgr;
if ( haveExif ) {
- exif.ParseMemoryStream ( this->exifContents.c_str(), this->exifContents.size() );
+ exif.ParseMemoryStream ( this->exifContents.c_str(), (XMP_Uns32)this->exifContents.size() );
}
if ( ! this->psirContents.empty() ) {
- psir.ParseMemoryResources ( this->psirContents.c_str(), this->psirContents.size() );
+ psir.ParseMemoryResources ( this->psirContents.c_str(), (XMP_Uns32)this->psirContents.size() );
}
// Determine the last-legacy priority and do the reconciliation. For JPEG files, the relevant
@@ -643,7 +648,7 @@ void JPEG_MetaHandler::ProcessXMP()
XMP_Assert ( this->containsXMP );
// Common code takes care of packetInfo.charForm, .padSize, and .writeable.
XMP_StringPtr packetStr = this->xmpPacket.c_str();
- XMP_StringLen packetLen = this->xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
try {
this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
} catch ( ... ) {
@@ -677,7 +682,7 @@ void JPEG_MetaHandler::ProcessXMP()
if ( guidPos != this->extendedXMP.end() ) {
try {
XMP_StringPtr extStr = guidPos->second.c_str();
- XMP_StringLen extLen = guidPos->second.size();
+ XMP_StringLen extLen = (XMP_StringLen)guidPos->second.size();
SXMPMeta extXMP ( extStr, extLen );
SXMPUtils::MergeFromJPEG ( &this->xmpObj, extXMP );
} catch ( ... ) {
@@ -716,18 +721,24 @@ void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate )
if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks.
if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0;
- bool doInPlace = (oldPacketOffset != 0) && (oldPacketLength != 0); // ! Has old packet and new packet fits.
+ bool doInPlace = (this->xmpPacket.size() <= (size_t)this->packetInfo.length);
- if ( doInPlace && (! this->extendedXMP.empty()) ) doInPlace = false;
+ if ( ! this->extendedXMP.empty() ) doInPlace = false;
- if ( doInPlace && (this->exifMgr != 0) && (this->exifMgr->IsLegacyChanged()) ) doInPlace = false;
- if ( doInPlace && (this->psirMgr != 0) && (this->psirMgr->IsLegacyChanged()) ) doInPlace = false;
+ if ( (this->exifMgr != 0) && (this->exifMgr->IsLegacyChanged()) ) doInPlace = false;
+ if ( (this->psirMgr != 0) && (this->psirMgr->IsLegacyChanged()) ) doInPlace = false;
if ( doInPlace ) {
#if GatherPerformanceData
sAPIPerf->back().extraInfo += ", JPEG in-place update";
#endif
+
+ if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) {
+ // They ought to match, cheap to be sure.
+ size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size();
+ this->xmpPacket.append ( extraSpace, ' ' );
+ }
LFA_FileRef liveFile = this->parent->fileRef;
std::string & newPacket = this->xmpPacket;
@@ -735,7 +746,7 @@ void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate )
XMP_Assert ( newPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic.
LFA_Seek ( liveFile, oldPacketOffset, SEEK_SET );
- LFA_Write ( liveFile, newPacket.c_str(), newPacket.size() );
+ LFA_Write ( liveFile, newPacket.c_str(), (XMP_Int32)newPacket.size() );
} else {
@@ -851,7 +862,7 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so
segLen += 2; // ! Don't do above in case machine does 16 bit "+".
if ( ! CheckFileSpace ( sourceRef, &ioBuf, segLen ) ) XMP_Throw ( "Unexpected end to JPEG", kXMPErr_BadJPEG );
- LFA_Write ( destRef, ioBuf.ptr, segLen );
+ LFA_Write ( destRef, ioBuf.ptr, (XMP_Int32)segLen );
ioBuf.ptr += segLen;
}
@@ -880,10 +891,10 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so
SXMPUtils::PackageForJPEG ( this->xmpObj, &mainXMP, &extXMP, &extDigest );
XMP_Assert ( (extXMP.size() == 0) || (extDigest.size() == 32) );
- first4 = MakeUns32BE ( 0xFFE10000 + 2 + kMainXMPSignatureLength + mainXMP.size() );
+ first4 = MakeUns32BE ( 0xFFE10000 + 2 + kMainXMPSignatureLength + (XMP_Uns32)mainXMP.size() );
LFA_Write ( destRef, &first4, 4 );
LFA_Write ( destRef, kMainXMPSignatureString, kMainXMPSignatureLength );
- LFA_Write ( destRef, mainXMP.c_str(), mainXMP.size() );
+ LFA_Write ( destRef, mainXMP.c_str(), (XMP_Int32)mainXMP.size() );
size_t extPos = 0;
size_t extLen = extXMP.size();
@@ -893,18 +904,18 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so
size_t partLen = extLen;
if ( partLen > 65000 ) partLen = 65000;
- first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExtXMPPrefixLength + partLen );
+ first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExtXMPPrefixLength + (XMP_Uns32)partLen );
LFA_Write ( destRef, &first4, 4 );
LFA_Write ( destRef, kExtXMPSignatureString, kExtXMPSignatureLength );
- LFA_Write ( destRef, extDigest.c_str(), extDigest.size() );
+ LFA_Write ( destRef, extDigest.c_str(), (XMP_Int32)extDigest.size() );
- first4 = MakeUns32BE ( extXMP.size() );
+ first4 = MakeUns32BE ( (XMP_Int32)extXMP.size() );
LFA_Write ( destRef, &first4, 4 );
- first4 = MakeUns32BE ( extPos );
+ first4 = MakeUns32BE ( (XMP_Int32)extPos );
LFA_Write ( destRef, &first4, 4 );
- LFA_Write ( destRef, &extXMP[extPos], partLen );
+ LFA_Write ( destRef, &extXMP[extPos], (XMP_Int32)partLen );
extPos += partLen;
extLen -= partLen;
@@ -973,7 +984,7 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so
}
}
- if ( copySegment ) LFA_Write ( destRef, ioBuf.ptr, 2+segLen );
+ if ( copySegment ) LFA_Write ( destRef, ioBuf.ptr, (XMP_Int32)(2+segLen) );
ioBuf.ptr += 2+segLen;
@@ -982,13 +993,13 @@ void JPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & so
// Copy the remainder of the source file.
size_t bufTail = ioBuf.len - (ioBuf.ptr - &ioBuf.data[0]);
- LFA_Write ( destRef, ioBuf.ptr, bufTail );
+ LFA_Write ( destRef, ioBuf.ptr, (XMP_Int32)bufTail );
ioBuf.ptr += bufTail;
while ( true ) {
RefillBuffer ( sourceRef, &ioBuf );
if ( ioBuf.len == 0 ) break;
- LFA_Write ( destRef, ioBuf.ptr, ioBuf.len );
+ LFA_Write ( destRef, ioBuf.ptr, (XMP_Int32)ioBuf.len );
ioBuf.ptr += ioBuf.len;
}
diff --git a/source/XMPFiles/FileHandlers/MOV_Handler.cpp b/source/XMPFiles/FileHandlers/MOV_Handler.cpp
index d7b6dcd..e2ea26c 100644
--- a/source/XMPFiles/FileHandlers/MOV_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/MOV_Handler.cpp
@@ -1,13 +1,16 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
-#if WIN_ENV
+#include "XMP_Environment.h" // ! This must be the first include.
+#if ! ( XMP_64 || XMP_UNIXBuild) // Closes at very bottom.
+
+#if XMP_WinBuild
#pragma warning ( disable : 4996 ) // '...' was declared deprecated
#endif
@@ -30,6 +33,14 @@ using namespace std;
static OSType kXMPUserDataType = 'XMP_';
static long kXMPUserDataTypeIndex = 1;
+static bool CreatorAtom_SetProperties ( SXMPMeta& xmpObj,
+ const MOV_MetaHandler::CreatorAtomStrings& creatorAtomStrings );
+
+static bool CreatorAtom_Update ( SXMPMeta& xmpObj, UserData& movieUserData );
+
+static bool CreatorAtom_ReadStrings ( MOV_MetaHandler::CreatorAtomStrings& creatorAtomStrings,
+ UserData& movieUserData );
+
// =================================================================================================
/// \file MOV_Handler.cpp
/// \brief File format handler for MOV.
@@ -48,7 +59,6 @@ XMPFileHandler * MOV_MetaHandlerCTor ( XMPFiles * parent )
} // MOV_MetaHandlerCTor
-
// =================================================================================================
// MOV_CheckFormat
// ===============
@@ -108,7 +118,7 @@ EXIT:
// ================================
MOV_MetaHandler::MOV_MetaHandler ( XMPFiles * _parent )
- : mQTInit(false), mMovieDataRef(0), mMovieDataHandler(0), mMovie(NULL), mMovieResourceID(0), mFilePermission(0)
+ : mQTInit(false), mMovieDataRef(0), mMovieDataHandler(0), mMovie(0), mMovieResourceID(0), mFilePermission(0)
{
this->parent = _parent;
@@ -131,6 +141,26 @@ MOV_MetaHandler::~MOV_MetaHandler()
} // MOV_MetaHandler::~MOV_MetaHandler
// =================================================================================================
+// MOV_MetaHandler::ProcessXMP
+// ===========================
+
+void MOV_MetaHandler::ProcessXMP()
+{
+ if ( (!this->containsXMP) || this->processedXMP ) return;
+
+ if ( this->handlerFlags & kXMPFiles_CanReconcile ) {
+ XMP_Throw ( "Reconciling file handlers must implement ProcessXMP", kXMPErr_InternalFailure );
+ }
+
+ SXMPUtils::RemoveProperties ( &this->xmpObj, 0, 0, kXMPUtil_DoAllProperties );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ this->processedXMP = true;
+
+ CreatorAtom_SetProperties ( this->xmpObj, mCreatorAtomStrings );
+
+}
+
+// =================================================================================================
// MOV_MetaHandler::UpdateFile
// ===========================
@@ -140,7 +170,7 @@ void MOV_MetaHandler::UpdateFile ( bool doSafeUpdate )
if ( doSafeUpdate ) XMP_Throw ( "MOV_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
XMP_StringPtr packetStr = this->xmpPacket.c_str();
- XMP_StringLen packetLen = this->xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
if ( packetLen == 0 ) return; // Bail if no XMP packet
@@ -152,7 +182,7 @@ void MOV_MetaHandler::UpdateFile ( bool doSafeUpdate )
OSErr err;
// Remove previous versions
- err = GetUserData ( movieUserData, NULL, kXMPUserDataType, kXMPUserDataTypeIndex );
+ err = GetUserData ( movieUserData, 0, kXMPUserDataType, kXMPUserDataTypeIndex );
if ( err == noErr ) {
RemoveUserData(movieUserData, kXMPUserDataType, kXMPUserDataTypeIndex);
}
@@ -167,6 +197,8 @@ void MOV_MetaHandler::UpdateFile ( bool doSafeUpdate )
DisposeHandle ( XMPdata );
}
+ CreatorAtom_Update ( this->xmpObj, movieUserData );
+
}
}
@@ -263,12 +295,230 @@ void MOV_MetaHandler::CloseMovie()
} // MOV_MetaHandler::CloseMovie
// =================================================================================================
+// GetAtomInfo
+// ===========
+
+struct AtomInfo {
+ XMP_Int64 atomSize;
+ XMP_Uns32 atomType;
+ bool hasLargeSize;
+};
+
+enum { // ! Do not rearrange, code depends on this order.
+ kBadQT_NoError = 0, // No errors.
+ kBadQT_SmallInner = 1, // An extra 1..7 bytes at the end of an inner span.
+ kBadQT_LargeInner = 2, // More serious inner garbage, found as invalid atom length.
+ kBadQT_SmallOuter = 3, // An extra 1..7 bytes at the end of the file.
+ kBadQT_LargeOuter = 4 // More serious EOF garbage, found as invalid atom length.
+};
+typedef XMP_Uns8 QTErrorMode;
+
+static QTErrorMode GetAtomInfo ( const LFA_FileRef qtFile, XMP_Int64 spanSize, int nesting, AtomInfo * info )
+{
+ QTErrorMode status = kBadQT_NoError;
+ XMP_Uns8 buffer [8];
+
+ info->hasLargeSize = false;
+
+ LFA_Read ( qtFile, buffer, 8, kLFA_RequireAll ); // Will throw if 8 bytes aren't available.
+ info->atomSize = GetUns32BE ( &buffer[0] ); // ! Yes, the initial size is big endian UInt32.
+ info->atomType = GetUns32BE ( &buffer[4] );
+
+ if ( info->atomSize == 0 ) { // Does the atom extend to EOF?
+
+ if ( nesting != 0 ) return kBadQT_LargeInner;
+ info->atomSize = spanSize; // This outer atom goes to EOF.
+
+ } else if ( info->atomSize == 1 ) { // Does the atom have a 64-bit size?
+
+ if ( spanSize < 16 ) { // Is there room in the span for the 16 byte header?
+ status = kBadQT_LargeInner;
+ if ( nesting == 0 ) status += 2; // Convert to "outer".
+ return status;
+ }
+
+ LFA_Read ( qtFile, buffer, 8, kLFA_RequireAll );
+ info->atomSize = (XMP_Int64) GetUns64BE ( &buffer[0] );
+ info->hasLargeSize = true;
+
+ }
+
+ XMP_Assert ( status == kBadQT_NoError );
+ return status;
+
+} // GetAtomInfo
+
+// =================================================================================================
+// CheckAtomList
+// =============
+//
+// Check that a sequence of atoms fills a given span. The I/O position must be at the start of the
+// span, it is left just past the span on success. Recursive checks are done for top level 'moov'
+// atoms, and second level 'udta' atoms ('udta' inside 'moov').
+//
+// Checking continues for "small inner" errors. They will be reported if no other kinds of errors
+// are found, otherwise the other error is reported. Checking is immediately aborted for any "large"
+// error. The rationale is that QuickTime can apparently handle small inner errors. They might be
+// arise from updates that shorten an atom by less than 8 bytes. Larger shrinkage should introduce a
+// 'free' atom.
+
+static QTErrorMode CheckAtomList ( const LFA_FileRef qtFile, XMP_Int64 spanSize, int nesting )
+{
+ QTErrorMode status = kBadQT_NoError;
+ AtomInfo info;
+
+ const static XMP_Uns32 moovAtomType = 0x6D6F6F76; // ! Don't use MakeUns32BE, already big endian.
+ const static XMP_Uns32 udtaAtomType = 0x75647461;
+
+ for ( ; spanSize >= 8; spanSize -= info.atomSize ) {
+
+ QTErrorMode atomStatus = GetAtomInfo ( qtFile, spanSize, nesting, &info );
+ if ( atomStatus != kBadQT_NoError ) return atomStatus;
+
+ XMP_Int64 headerSize = 8;
+ if ( info.hasLargeSize ) headerSize = 16;
+
+ if ( (info.atomSize < headerSize) || (info.atomSize > spanSize) ) {
+ status = kBadQT_LargeInner;
+ if ( nesting == 0 ) status += 2; // Convert to "outer".
+ return status;
+ }
+
+ bool doChildren = false;
+ if ( (nesting == 0) && (info.atomType == moovAtomType) ) doChildren = true;
+ if ( (nesting == 1) && (info.atomType == udtaAtomType) ) doChildren = true;
+
+ XMP_Int64 dataSize = info.atomSize - headerSize;
+
+ if ( ! doChildren ) {
+ LFA_Seek ( qtFile, dataSize, SEEK_CUR );
+ } else {
+ QTErrorMode innerStatus = CheckAtomList ( qtFile, dataSize, nesting+1 );
+ if ( innerStatus > kBadQT_SmallInner ) return innerStatus; // Quit for serious errors.
+ if ( status == kBadQT_NoError ) status = innerStatus; // Remember small inner errors.
+ }
+
+ }
+
+ XMP_Assert ( status <= kBadQT_SmallInner ); // Else already returned.
+ // ! Make sure inner kBadQT_SmallInner is propagated if this span is OK.
+
+ if ( spanSize != 0 ) {
+ LFA_Seek ( qtFile, spanSize, SEEK_CUR ); // ! Skip the trailing garbage of this span.
+ status = kBadQT_SmallInner;
+ if ( spanSize >= 8 ) status = kBadQT_LargeInner;
+ if ( nesting == 0 ) status += 2; // Convert to "outer".
+ }
+
+ return status;
+
+} // CheckAtomList
+
+// =================================================================================================
+// AttemptFileRepair
+// =================
+
+static void AttemptFileRepair ( LFA_FileRef qtFile, XMP_Int64 fileSpace, QTErrorMode status )
+{
+
+ switch ( status ) {
+ case kBadQT_NoError : return; // Sanity check.
+ case kBadQT_SmallInner : return; // Ignore these, QT seems to be able to handle them.
+ case kBadQT_LargeInner : XMP_Throw ( "Can't repair QuickTime file", kXMPErr_BadFileFormat );
+ case kBadQT_SmallOuter : break; // Truncate file below.
+ case kBadQT_LargeOuter : break; // Truncate file below.
+ default : XMP_Throw ( "Invalid QuickTime error mode", kXMPErr_InternalFailure );
+ }
+
+ AtomInfo info;
+ XMP_Int64 headerSize;
+
+ // Process the top level atoms until an error is found.
+
+ LFA_Seek ( qtFile, 0, SEEK_SET );
+
+ for ( ; fileSpace >= 8; fileSpace -= info.atomSize ) {
+
+ QTErrorMode atomStatus = GetAtomInfo ( qtFile, fileSpace, 0, &info );
+
+ headerSize = 8; // ! Set this before checking atomStatus, used after the loop.
+ if ( info.hasLargeSize ) headerSize = 16;
+
+ if ( atomStatus != kBadQT_NoError ) break;
+ if ( (info.atomSize < headerSize) || (info.atomSize > fileSpace) ) break;
+
+ XMP_Int64 dataSize = info.atomSize - headerSize;
+ LFA_Seek ( qtFile, dataSize, SEEK_CUR );
+
+ }
+
+ // Truncate the file. If fileSpace >= 8 then the loop exited early due to a bad atom, seek back
+ // to the atom's start. Otherwise, the loop exited because no mmore atoms are possible, no seek.
+
+ if ( fileSpace >= 8 ) LFA_Seek ( qtFile, -headerSize, SEEK_CUR );
+ XMP_Int64 currPos = LFA_Tell ( qtFile );
+ LFA_Truncate ( qtFile, currPos );
+
+} // AttemptFileRepair
+
+// =================================================================================================
+// CheckFileStructure
+// ==================
+
+static void CheckFileStructure ( XMPFileHandler * thiz, bool doRepair )
+{
+ XMPFiles * parent = thiz->parent;
+
+ // Open the disk file so we can look inside and maybe repair.
+
+ AutoFile localFile; // ! Don't use parent->fileRef keep this usage private.
+ XMP_Assert ( parent->fileRef == 0 ); // The file should not be open yet.
+
+ char openMode = 'r';
+ if ( doRepair ) openMode = 'w';
+ localFile.fileRef = LFA_Open ( parent->filePath.c_str(), openMode );
+ if ( localFile.fileRef == 0 ) XMP_Throw ( "Can't open QuickTime file for update checks", kXMPErr_ExternalFailure );
+ XMP_Int64 fileSize = LFA_Measure ( localFile.fileRef );
+
+ // Check the basic file structure and try to repair if asked.
+
+ QTErrorMode status = CheckAtomList ( localFile.fileRef, fileSize, 0 );
+
+ if ( status != kBadQT_NoError ) {
+ if ( doRepair ) {
+ AttemptFileRepair ( localFile.fileRef, fileSize, status ); // Will throw if the attempt fails.
+ } else if ( status != kBadQT_SmallInner ) {
+ XMP_Throw ( "Ill-formed QuickTime file", kXMPErr_BadFileFormat );
+ } else {
+ return; // ! Ignore these, QT seems to be able to handle them.
+ // *** Might want to throw for check-only, ignore when repairing.
+ }
+ }
+
+} // CheckFileStructure;
+
+
+// =================================================================================================
// MOV_MetaHandler::CacheFileData
// ==============================
void MOV_MetaHandler::CacheFileData()
{
+ // Pre-check files opened for update. We've found bugs in Apple's QT code that make slightly
+ // ill-formed files unreadable.
+
+ XMPFiles * parent = this->parent;
+
+ const bool isUpdate = XMP_OptionIsSet ( parent->openFlags, kXMPFiles_OpenForUpdate );
+ const bool doRepair = XMP_OptionIsSet ( parent->openFlags, kXMPFiles_OpenRepairFile );
+
+ if ( isUpdate ) {
+ CheckFileStructure ( this, doRepair ); // Will throw for failure.
+ }
+
+ // Continue with the usual caching of the file's metadata.
+
this->containsXMP = false;
if ( this->OpenMovie ( kDataHCanRead ) ) {
@@ -297,6 +547,8 @@ void MOV_MetaHandler::CacheFileData()
this->packetInfo.length = (XMP_Int32)dataSize;
this->containsXMP = true;
+ CreatorAtom_ReadStrings ( mCreatorAtomStrings, movieUserData );
+
}
DisposeHandle ( XMPdataHandle );
@@ -310,3 +562,435 @@ void MOV_MetaHandler::CacheFileData()
} // MOV_MetaHandler::CacheFileData
// =================================================================================================
+// =================================================================================================
+
+// *** Could be pulled out, maybe refactored and partly shared with AVI and WAV.
+
+#pragma pack(push,1)
+
+// [TODO] Can we switch to using just a full path here?
+struct FSSpecLegacy
+{
+ short vRefNum;
+ long parID;
+ char name[260]; // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp -- 260 is "old school", 32000 is "new school".
+};
+
+struct CR8R_CreatorAtom
+{
+ unsigned long magicLu;
+
+ long atom_sizeL; // Size of this structure.
+ short atom_vers_majorS; // Major atom version.
+ short atom_vers_minorS; // Minor atom version.
+
+ // mac
+ unsigned long creator_codeLu; // Application code on MacOS.
+ unsigned long creator_eventLu; // Invocation appleEvent.
+
+ // windows
+ char creator_extAC[16]; // Extension allowing registry search to app.
+ char creator_flagAC[16]; // Flag passed to app at invocation time.
+
+ char creator_nameAC[32]; // Name of the creator application.
+};
+
+typedef CR8R_CreatorAtom** CR8R_CreatorAtomHandle;
+
+typedef enum
+{
+ Embed_ExportTypeMovie = 0,
+ Embed_ExportTypeStill,
+ Embed_ExportTypeAudio,
+ Embed_ExportTypeCustom
+}
+Embed_ExportType;
+
+
+struct Embed_ProjectLinkAtom
+{
+ // header data
+ unsigned long magicLu;
+ long atom_sizeL;
+ short atom_vers_apiS;
+ short atom_vers_codeS;
+
+ // the link data
+ unsigned long exportType; // See enum. The type of export that generated the file
+
+ // [TODO] Can we switch to using just a full path here?
+ FSSpecLegacy fullPath; // Full path of the project file
+};
+
+#pragma pack(pop)
+
+// -------------------------------------------------------------------------------------------------
+
+#define kCreatorTool "CreatorTool"
+#define AdobeCreatorAtomVersion_Major 1
+#define AdobeCreatorAtomVersion_Minor 0
+#define AdobeCreatorAtom_Magic 0xBEEFCAFE
+
+#define myCreatorAtom MakeFourCC ( 'C','r','8','r' )
+
+static void CreatorAtom_Initialize ( CR8R_CreatorAtom& creatorAtom )
+{
+ memset ( &creatorAtom, 0, sizeof(CR8R_CreatorAtom) );
+ creatorAtom.magicLu = AdobeCreatorAtom_Magic;
+ creatorAtom.atom_vers_majorS = AdobeCreatorAtomVersion_Major;
+ creatorAtom.atom_vers_minorS = AdobeCreatorAtomVersion_Minor;
+ creatorAtom.atom_sizeL = sizeof(CR8R_CreatorAtom);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+#define PR_PROJECT_LINK_ATOM_VERS_API 1
+#define PR_PROJECT_LINK_ATOM_VERS_CODE 0
+#define PR_PROJECT_LINK_ATOM_TYPE 'PrmL'
+#define PR_PROJECT_LINK_MAGIC 0x600DF00D // GoodFood
+
+#define myProjectLink MakeFourCC ( 'P','r','m','L')
+
+// -------------------------------------------------------------------------------------------------
+
+static void CreatorAtom_MakeValid ( CR8R_CreatorAtom * creator_atomP )
+{
+ // If already valid, no conversion is needed.
+ if ( creator_atomP->magicLu == AdobeCreatorAtom_Magic ) return;
+
+ Flip4 ( &creator_atomP->magicLu );
+ Flip2 ( &creator_atomP->atom_vers_majorS );
+ Flip2 ( &creator_atomP->atom_vers_minorS );
+
+ Flip4 ( &creator_atomP->atom_sizeL );
+ Flip4 ( &creator_atomP->creator_codeLu );
+ Flip4 ( &creator_atomP->creator_eventLu );
+
+ XMP_Assert ( creator_atomP->magicLu == AdobeCreatorAtom_Magic );
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static void CreatorAtom_ToBE ( CR8R_CreatorAtom * creator_atomP )
+{
+ creator_atomP->atom_vers_majorS = MakeUns16BE ( creator_atomP->atom_vers_majorS );
+ creator_atomP->atom_vers_minorS = MakeUns16BE ( creator_atomP->atom_vers_minorS );
+
+ creator_atomP->magicLu = MakeUns32BE ( creator_atomP->magicLu );
+ creator_atomP->atom_sizeL = MakeUns32BE ( creator_atomP->atom_sizeL );
+ creator_atomP->creator_codeLu = MakeUns32BE ( creator_atomP->creator_codeLu );
+ creator_atomP->creator_eventLu = MakeUns32BE ( creator_atomP->creator_eventLu );
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static void ProjectLinkAtom_MakeValid ( Embed_ProjectLinkAtom * link_atomP )
+{
+ // If already valid, no conversion is needed.
+ if ( link_atomP->magicLu == PR_PROJECT_LINK_MAGIC ) return;
+
+ // do the header
+ Flip4 ( &link_atomP->magicLu );
+ Flip4 ( &link_atomP->atom_sizeL );
+ Flip2 ( &link_atomP->atom_vers_apiS );
+ Flip2 ( &link_atomP->atom_vers_codeS );
+
+ // do the FSSpec data
+ Flip2 ( &link_atomP->fullPath.vRefNum );
+ Flip4 ( &link_atomP->fullPath.parID );
+
+ XMP_Assert ( link_atomP->magicLu == PR_PROJECT_LINK_MAGIC );
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static void ProjectLinkAtom_Initialize ( Embed_ProjectLinkAtom& epla, Embed_ExportType type,
+ const std::string& projectPathString)
+{
+
+ memset ( &epla, 0, sizeof(Embed_ProjectLinkAtom) );
+
+ epla.magicLu = PR_PROJECT_LINK_MAGIC;
+ epla.atom_sizeL = epla.atom_vers_apiS = PR_PROJECT_LINK_ATOM_VERS_API;
+ epla.atom_vers_codeS = PR_PROJECT_LINK_ATOM_VERS_CODE;
+ epla.atom_sizeL = sizeof(Embed_ProjectLinkAtom);
+ epla.exportType = type;
+ epla.fullPath.vRefNum = 0;
+ epla.fullPath.parID = 0;
+
+ strncpy ( epla.fullPath.name, projectPathString.c_str(),
+ min ( projectPathString.length(), sizeof(epla.fullPath.name) ) );
+
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static bool Mov_ReadProjectLinkAtom ( UserData& movieUserData, Embed_ProjectLinkAtom* epla )
+{
+ Handle PrmLdataHandle ( NewHandle(0) );
+ if ( PrmLdataHandle == 0 ) return false;
+
+ OSErr err = GetUserData ( movieUserData, PrmLdataHandle, 'PrmL', 1 );
+ if ( err != noErr ) return false;
+
+ // Convert handles data, to std::string raw
+ // std::string PrmLPacket;
+ // PrmLPacket.clear();
+ size_t dataSize = GetHandleSize ( PrmLdataHandle );
+ HLock ( PrmLdataHandle );
+
+ bool ok = (dataSize == sizeof(Embed_ProjectLinkAtom));
+ if ( ok ) {
+ memcpy ( epla, (*PrmLdataHandle), dataSize );
+ ProjectLinkAtom_MakeValid ( epla );
+ }
+
+ HUnlock ( PrmLdataHandle );
+ DisposeHandle ( PrmLdataHandle );
+
+ return ok;
+
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static bool Mov_ReadCreatorAtom ( UserData& movieUserData, CR8R_CreatorAtom* creatorAtom )
+{
+ Handle PrmLdataHandle ( NewHandle(0) );
+ if ( PrmLdataHandle == 0 ) return false;
+
+ OSErr err = GetUserData ( movieUserData, PrmLdataHandle, 'Cr8r', 1 );
+ if ( err != noErr ) return false;
+
+ // Convert handles data, to std::string raw
+ // std::string PrmLPacket;
+ // PrmLPacket.clear();
+ size_t dataSize = GetHandleSize ( PrmLdataHandle );
+ HLock ( PrmLdataHandle );
+
+ bool ok = (sizeof(CR8R_CreatorAtom) == dataSize);
+ if ( ok ) {
+ memcpy ( creatorAtom, (*PrmLdataHandle), dataSize );
+ CreatorAtom_MakeValid ( creatorAtom );
+ }
+
+ HUnlock ( PrmLdataHandle );
+ DisposeHandle ( PrmLdataHandle );
+
+ return ok;
+
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static bool Mov_WriteCreatorAtom ( UserData& movieUserData, CR8R_CreatorAtom& creatorAtom, bool mustExist )
+{
+ Handle PrmLdataHandle ( NewHandle(sizeof(CR8R_CreatorAtom)) );
+ if ( PrmLdataHandle == 0 ) return false;
+
+ OSErr err = GetUserData ( movieUserData, PrmLdataHandle, 'Cr8r', 1);
+ if ( err == noErr ) {
+ while ( ! RemoveUserData ( movieUserData, 'Cr8r', 1 ) ) {};
+ } else {
+ if ( mustExist ) return false;
+ }
+
+ // Convert handles data, to std::string raw
+ // std::string PrmLPacket;
+ // PrmLPacket.clear();
+ size_t dataSize = GetHandleSize ( PrmLdataHandle );
+ HLock ( PrmLdataHandle );
+ memcpy ( (*PrmLdataHandle), &creatorAtom, sizeof(CR8R_CreatorAtom) );
+ CreatorAtom_ToBE ( (CR8R_CreatorAtom*)(*PrmLdataHandle) );
+ HUnlock ( PrmLdataHandle );
+
+ OSErr movieErr = AddUserData ( movieUserData, PrmLdataHandle, 'Cr8r');
+
+ DisposeHandle ( PrmLdataHandle );
+
+ return movieErr==0;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+#define DoAssignBufferedString(str,buffer) AssignBufferedString ( str, buffer, sizeof(buffer) )
+
+static inline void AssignBufferedString ( std::string & str, const char * buffer, size_t maxLen )
+{
+ size_t len;
+ for ( len = 0; (len < maxLen) && (buffer[len] != 0); ++len ) { /* empty body */ }
+ str.assign ( buffer, len );
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static bool CreatorAtom_ReadStrings ( MOV_MetaHandler::CreatorAtomStrings& creatorAtomStrings,
+ UserData& movieUserData )
+{
+ Embed_ProjectLinkAtom epla;
+ bool ok = Mov_ReadProjectLinkAtom ( movieUserData, &epla );
+
+ if ( ok ) {
+
+ std::string projectPathString = epla.fullPath.name;
+
+ if ( ! projectPathString.empty() ) {
+
+ if ( projectPathString[0] == '/' ) {
+ creatorAtomStrings.posixProjectPath = projectPathString;
+ } else if ( projectPathString.substr(0,4) == std::string("\\\\?\\") ) {
+ creatorAtomStrings.uncProjectPath = projectPathString;
+ }
+
+ switch ( epla.exportType ) {
+ case Embed_ExportTypeMovie : creatorAtomStrings.projectRefType = "movie"; break;
+ case Embed_ExportTypeStill : creatorAtomStrings.projectRefType = "still"; break;
+ case Embed_ExportTypeAudio : creatorAtomStrings.projectRefType = "audio"; break;
+ case Embed_ExportTypeCustom : creatorAtomStrings.projectRefType = "custom"; break;
+ }
+
+ }
+
+ }
+
+ CR8R_CreatorAtom creatorAtom;
+ ok = Mov_ReadCreatorAtom ( movieUserData, &creatorAtom );
+
+ if ( ok ) {
+
+ char buffer[256];
+
+ sprintf ( buffer, "%d", creatorAtom.creator_codeLu );
+ creatorAtomStrings.applicationCode = buffer;
+
+ sprintf ( buffer, "%d", creatorAtom.creator_eventLu );
+ creatorAtomStrings.invocationAppleEvent = buffer;
+
+ DoAssignBufferedString ( creatorAtomStrings.extension, creatorAtom.creator_extAC );
+ DoAssignBufferedString ( creatorAtomStrings.invocationFlags, creatorAtom.creator_flagAC );
+ DoAssignBufferedString ( creatorAtomStrings.creatorTool, creatorAtom.creator_nameAC );
+
+ }
+
+ return ok;
+
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static bool CreatorAtom_SetProperties ( SXMPMeta& xmpObj,
+ const MOV_MetaHandler::CreatorAtomStrings& creatorAtomStrings )
+{
+ if ( ! creatorAtomStrings.posixProjectPath.empty() ) {
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom",
+ kXMP_NS_CreatorAtom, "posixProjectPath", creatorAtomStrings.posixProjectPath, 0 );
+ }
+
+ if ( ! creatorAtomStrings.uncProjectPath.empty() ) {
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom",
+ kXMP_NS_CreatorAtom, "uncProjectPath", creatorAtomStrings.uncProjectPath, 0 );
+ }
+
+ if ( ! creatorAtomStrings.projectRefType.empty() ) {
+ xmpObj.SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", creatorAtomStrings.projectRefType.c_str());
+ }
+
+ if ( ! creatorAtomStrings.applicationCode.empty() ) {
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom",
+ kXMP_NS_CreatorAtom, "applicationCode", creatorAtomStrings.applicationCode, 0 );
+ }
+
+ if ( ! creatorAtomStrings.invocationAppleEvent.empty() ) {
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom",
+ kXMP_NS_CreatorAtom, "invocationAppleEvent", creatorAtomStrings.invocationAppleEvent, 0 );
+ }
+
+ if ( ! creatorAtomStrings.extension.empty() ) {
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom",
+ kXMP_NS_CreatorAtom, "extension", creatorAtomStrings.extension, 0 );
+ }
+
+ if ( ! creatorAtomStrings.invocationFlags.empty() ) {
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom",
+ kXMP_NS_CreatorAtom, "invocationFlags", creatorAtomStrings.invocationFlags, 0 );
+ }
+
+ if ( ! creatorAtomStrings.creatorTool.empty() ) {
+ xmpObj.SetProperty ( kXMP_NS_XMP, "CreatorTool", creatorAtomStrings.creatorTool, 0 );
+ }
+
+ return ok;
+
+}
+
+// -------------------------------------------------------------------------------------------------
+
+#define EnsureFinalNul(buffer) buffer [ sizeof(buffer) - 1 ] = 0
+
+static bool CreatorAtom_Update ( SXMPMeta& xmpObj,
+ UserData& movieUserData )
+{
+
+ // Get Creator Atom XMP values.
+ bool found = false;
+ std::string posixPathString, uncPathString;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "posixProjectPath", &posixPathString, 0 ) ) found = true;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "uncProjectPath", &uncPathString, 0 ) ) found = true;
+
+ std::string applicationCodeString, invocationAppleEventString, extensionString, invocationFlagsString, creatorToolString;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &applicationCodeString, 0 ) ) found = true;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &invocationAppleEventString, 0 ) ) found = true;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &extensionString, 0 ) ) found = true;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &invocationFlagsString, 0 ) ) found = true;
+ if ( xmpObj.GetProperty ( kXMP_NS_XMP, "CreatorTool", &creatorToolString, 0 ) ) found = true;
+
+ // If no Creator Atom information found, don't write anything.
+ if ( ! found ) return false;
+
+ // Read Legacy Creator Atom.
+ unsigned long creatorAtomSize = 0;
+ CR8R_CreatorAtom creatorAtomLegacy;
+ CreatorAtom_Initialize ( creatorAtomLegacy );
+ bool ok = Mov_ReadCreatorAtom ( movieUserData, &creatorAtomLegacy );
+
+ // Generate new Creator Atom from XMP.
+ CR8R_CreatorAtom creatorAtomViaXMP;
+ CreatorAtom_Initialize ( creatorAtomViaXMP );
+
+ if ( ! applicationCodeString.empty() ) {
+ creatorAtomViaXMP.creator_codeLu = strtoul ( applicationCodeString.c_str(), 0, 0 );
+ }
+ if ( ! invocationAppleEventString.empty() ) {
+ creatorAtomViaXMP.creator_eventLu = strtoul ( invocationAppleEventString.c_str(), 0, 0 );
+ }
+ if ( ! extensionString.empty() ) {
+ strncpy ( creatorAtomViaXMP.creator_extAC, extensionString.c_str(), sizeof(creatorAtomViaXMP.creator_extAC) );
+ EnsureFinalNul ( creatorAtomViaXMP.creator_extAC );
+ }
+ if ( ! invocationFlagsString.empty() ) {
+ strncpy ( creatorAtomViaXMP.creator_flagAC, invocationFlagsString.c_str(), sizeof(creatorAtomViaXMP.creator_flagAC) );
+ EnsureFinalNul ( creatorAtomViaXMP.creator_flagAC );
+ }
+ if ( ! creatorToolString.empty() ) {
+ strncpy ( creatorAtomViaXMP.creator_nameAC, creatorToolString.c_str(), sizeof(creatorAtomViaXMP.creator_nameAC) );
+ EnsureFinalNul ( creatorAtomViaXMP.creator_nameAC );
+ }
+
+ // Write Creator Atom.
+ if ( ok ) {
+ // If there's legacy, update if atom generated from XMP doesn't match legacy.
+ if ( memcmp ( &creatorAtomViaXMP, &creatorAtomLegacy, sizeof(CR8R_CreatorAtom) ) != 0 ) {
+ ok = Mov_WriteCreatorAtom ( movieUserData, creatorAtomViaXMP, true );
+ }
+ } else {
+ // Write completely new atom from XMP.
+ ok = Mov_WriteCreatorAtom ( movieUserData, creatorAtomViaXMP, false );
+ }
+
+ return ok;
+
+}
+
+// =================================================================================================
+
+#endif // XMP_64 || XMP_UNIXBuild
diff --git a/source/XMPFiles/FileHandlers/MOV_Handler.hpp b/source/XMPFiles/FileHandlers/MOV_Handler.hpp
index 9c7f8c3..4d398ce 100644
--- a/source/XMPFiles/FileHandlers/MOV_Handler.hpp
+++ b/source/XMPFiles/FileHandlers/MOV_Handler.hpp
@@ -3,13 +3,16 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
+#include "XMP_Environment.h" // ! This must be the first include.
+#if ! ( XMP_64 || XMP_UNIXBuild) // Closes at very bottom.
+
#include "XMPFiles_Impl.hpp"
// Include these first to prevent collision with CIcon
@@ -53,10 +56,24 @@ public:
~MOV_MetaHandler();
void CacheFileData();
+ void ProcessXMP();
void UpdateFile ( bool doSafeUpdate );
void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath );
+ struct CreatorAtomStrings {
+ std::string posixProjectPath;
+ std::string uncProjectPath;
+ std::string projectRefType;
+ std::string applicationCode;
+ std::string invocationAppleEvent;
+ std::string extension;
+ std::string invocationFlags;
+ std::string creatorTool;
+ };
+
+ CreatorAtomStrings mCreatorAtomStrings; // ! Public so utility code can muck with them.
+
protected:
bool mQTInit;
@@ -73,4 +90,5 @@ protected:
// =================================================================================================
+#endif // XMP_64 || XMP_UNIXBuild
#endif /* __MOV_Handler_hpp__ */
diff --git a/source/XMPFiles/FileHandlers/MP3_Handler.cpp b/source/XMPFiles/FileHandlers/MP3_Handler.cpp
index 56dcf09..f906900 100644
--- a/source/XMPFiles/FileHandlers/MP3_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/MP3_Handler.cpp
@@ -7,6 +7,9 @@
// of the Adobe license agreement accompanying it.
// =================================================================================================
+#include "XMP_Environment.h" // ! This must be the first include.
+#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed.
+
#include "MP3_Handler.hpp"
#include "ID3_Support.hpp"
@@ -136,7 +139,7 @@ void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate )
bool fReconciliate = !(this->parent->openFlags & kXMPFiles_OpenOnlyXMP);
XMP_StringPtr packetStr = xmpPacket.c_str();
- XMP_StringLen packetLen = xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
if ( packetLen == 0 ) return;
LFA_FileRef fileRef ( this->parent->fileRef );
@@ -159,42 +162,42 @@ void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate )
std::string strTitle;
this->xmpObj.GetLocalizedText ( kXMP_NS_DC, kTitle, "", "x-default", 0, &strTitle, 0 );
ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion,
- mp3TitleChunk, strTitle.c_str(), strTitle.size() );
+ mp3TitleChunk, strTitle.c_str(), (unsigned long)strTitle.size() );
std::string strDate;
this->xmpObj.GetProperty ( kXMP_NS_XMP, kCreateDate, &strDate, 0 );
if ( bVersion == 4 ) {
ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion,
- mp3CreateDateChunk4, strDate.c_str(), strDate.size() );
+ mp3CreateDateChunk4, strDate.c_str(), (unsigned long)strDate.size() );
} else {
ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion,
- mp3CreateDateChunk3, strDate.c_str(), strDate.size() );
+ mp3CreateDateChunk3, strDate.c_str(), (unsigned long)strDate.size() );
}
std::string strArtist;
this->xmpObj.GetProperty ( kXMP_NS_DM, kArtist, &strArtist, 0 );
ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion, mp3ArtistChunk,
- strArtist.c_str(), strArtist.size() );
+ strArtist.c_str(), (unsigned long)strArtist.size() );
std::string strAlbum;
this->xmpObj.GetProperty ( kXMP_NS_DM, kAlbum, &strAlbum, 0 );
ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion,
- mp3AlbumChunk, strAlbum.c_str(), strAlbum.size() );
+ mp3AlbumChunk, strAlbum.c_str(), (unsigned long)strAlbum.size() );
std::string strGenre;
this->xmpObj.GetProperty ( kXMP_NS_DM, kGenre, &strGenre, 0 );
ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion,
- mp3GenreChunk, strGenre.c_str(), strGenre.size() );
+ mp3GenreChunk, strGenre.c_str(), (unsigned long)strGenre.size() );
std::string strComment;
this->xmpObj.GetProperty ( kXMP_NS_DM, kLogComment, &strComment, 0 );
ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion,
- mp3CommentChunk, strComment.c_str(), strComment.size() );
+ mp3CommentChunk, strComment.c_str(), (unsigned long)strComment.size() );
std::string strTrack;
this->xmpObj.GetProperty ( kXMP_NS_DM, kTrack, &strTrack, 0 );
ID3_Support::AddXMPTagToID3Buffer ( buffer, &dwCurOffset, bufferSize, bVersion,
- mp3TrackChunk, strTrack.c_str(), strTrack.size() );
+ mp3TrackChunk, strTrack.c_str(), (unsigned long)strTrack.size() );
}
@@ -257,7 +260,7 @@ void MP3_MetaHandler::CacheFileData()
this->packetInfo.offset = xmpOffset;
this->packetInfo.length = bufferSize;
this->xmpPacket.assign(buffer.data(), bufferSize);
- this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), this->xmpPacket.size() );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
this->containsXMP = true;
}
@@ -337,3 +340,7 @@ bool MP3_MetaHandler::LoadPropertyFromID3 ( LFA_FileRef inFileRef, char * strFra
return false;
} // WAV_MetaHandler::LoadPropertyFromID3
+
+// =================================================================================================
+
+#endif // XMP_UNIXBuild
diff --git a/source/XMPFiles/FileHandlers/MP3_Handler.hpp b/source/XMPFiles/FileHandlers/MP3_Handler.hpp
index f9aa46d..8a321e6 100644
--- a/source/XMPFiles/FileHandlers/MP3_Handler.hpp
+++ b/source/XMPFiles/FileHandlers/MP3_Handler.hpp
@@ -10,6 +10,9 @@
// of the Adobe license agreement accompanying it.
// =================================================================================================
+#include "XMP_Environment.h" // ! This must be the first include.
+#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed.
+
#include "XMPFiles_Impl.hpp"
// =================================================================================================
@@ -53,4 +56,5 @@ private:
// =================================================================================================
+#endif // XMP_UNIXBuild
#endif /* __MP3_Handler_hpp__ */
diff --git a/source/XMPFiles/FileHandlers/MPEG_Handler.cpp b/source/XMPFiles/FileHandlers/MPEG2_Handler.cpp
index 674c5e3..9c2eb48 100644
--- a/source/XMPFiles/FileHandlers/MPEG_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/MPEG2_Handler.cpp
@@ -7,19 +7,19 @@
// of the Adobe license agreement accompanying it.
// =================================================================================================
-#if WIN_ENV
+#if XMP_WinBuild
#pragma warning ( disable : 4996 ) // '...' was declared deprecated
#endif
-#include "MPEG_Handler.hpp"
+#include "MPEG2_Handler.hpp"
using namespace std;
// =================================================================================================
-/// \file MPEG_Handler.cpp
-/// \brief File format handler for MPEG.
+/// \file MPEG2_Handler.cpp
+/// \brief File format handler for MPEG2.
///
-/// BLECH! YUCK! GAG! MPEG is done using a sidecar and recognition only by file extension! BARF!!!!!
+/// BLECH! YUCK! GAG! MPEG-2 is done using a sidecar and recognition only by file extension! BARF!!!!!
///
// =================================================================================================
@@ -46,64 +46,64 @@ static inline XMP_StringPtr FindFileExtension ( XMP_StringPtr filePath )
} // FindFileExtension
// =================================================================================================
-// MPEG_MetaHandlerCTor
-// ====================
+// MPEG2_MetaHandlerCTor
+// =====================
-XMPFileHandler * MPEG_MetaHandlerCTor ( XMPFiles * parent )
+XMPFileHandler * MPEG2_MetaHandlerCTor ( XMPFiles * parent )
{
- return new MPEG_MetaHandler ( parent );
+ return new MPEG2_MetaHandler ( parent );
-} // MPEG_MetaHandlerCTor
+} // MPEG2_MetaHandlerCTor
// =================================================================================================
-// MPEG_CheckFormat
-// ================
+// MPEG2_CheckFormat
+// =================
-// The MPEG handler uses just the file extension, not the file content. Worse yet, it also uses a
+// The MPEG-2 handler uses just the file extension, not the file content. Worse yet, it also uses a
// sidecar file for the XMP. This works better if the handler owns the file, we open the sidecar
-// instead of the actual MPEG file.
+// instead of the actual MPEG-2 file.
-bool MPEG_CheckFormat ( XMP_FileFormat format,
+bool MPEG2_CheckFormat ( XMP_FileFormat format,
XMP_StringPtr filePath,
LFA_FileRef fileRef,
XMPFiles * parent )
{
IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(fileRef);
- XMP_Assert ( format == kXMP_MPEGFile );
+ XMP_Assert ( (format == kXMP_MPEGFile) || (format == kXMP_MPEG2File) );
XMP_Assert ( fileRef == 0 );
- return ( parent->format == kXMP_MPEGFile ); // ! Just use the first call's format hint.
+ return ( (parent->format == kXMP_MPEGFile) || (parent->format == kXMP_MPEGFile) ); // ! Just use the first call's format hint.
-} // MPEG_CheckFormat
+} // MPEG2_CheckFormat
// =================================================================================================
-// MPEG_MetaHandler::MPEG_MetaHandler
-// ==================================
+// MPEG2_MetaHandler::MPEG2_MetaHandler
+// ====================================
-MPEG_MetaHandler::MPEG_MetaHandler ( XMPFiles * _parent )
+MPEG2_MetaHandler::MPEG2_MetaHandler ( XMPFiles * _parent )
{
this->parent = _parent;
- this->handlerFlags = kMPEG_HandlerFlags;
+ this->handlerFlags = kMPEG2_HandlerFlags;
this->stdCharForm = kXMP_Char8Bit;
-} // MPEG_MetaHandler::MPEG_MetaHandler
+} // MPEG2_MetaHandler::MPEG2_MetaHandler
// =================================================================================================
-// MPEG_MetaHandler::~MPEG_MetaHandler
-// ===================================
+// MPEG2_MetaHandler::~MPEG2_MetaHandler
+// =====================================
-MPEG_MetaHandler::~MPEG_MetaHandler()
+MPEG2_MetaHandler::~MPEG2_MetaHandler()
{
// Nothing to do.
-} // MPEG_MetaHandler::~MPEG_MetaHandler
+} // MPEG2_MetaHandler::~MPEG2_MetaHandler
// =================================================================================================
-// MPEG_MetaHandler::CacheFileData
-// ===============================
+// MPEG2_MetaHandler::CacheFileData
+// ================================
-void MPEG_MetaHandler::CacheFileData()
+void MPEG2_MetaHandler::CacheFileData()
{
bool readOnly = (! (this->parent->openFlags & kXMPFiles_OpenForUpdate));
@@ -111,7 +111,7 @@ void MPEG_MetaHandler::CacheFileData()
this->processedXMP = true; // Whatever we do here is all that we do for XMPFiles::OpenFile.
// Try to open the sidecar XMP file. Tolerate an open failure, there might not be any XMP.
- // Note that MPEG_CheckFormat can't save the sidecar path because the handler doesn't exist then.
+ // Note that MPEG2_CheckFormat can't save the sidecar path because the handler doesn't exist then.
XMP_StringPtr filePath = this->parent->filePath.c_str();
XMP_StringPtr extPtr = FindFileExtension ( filePath );
@@ -139,7 +139,7 @@ void MPEG_MetaHandler::CacheFileData()
// Try to create a file if it does not yet exist.
// *** Could someday check for a permission failure versus no .xmp file.
this->parent->fileRef = LFA_Create ( this->sidecarPath.c_str() );
- if ( this->parent->fileRef == 0 ) XMP_Throw ( "Can't create MPEG sidecar", kXMPErr_ExternalFailure );
+ if ( this->parent->fileRef == 0 ) XMP_Throw ( "Can't create MPEG-2 sidecar", kXMPErr_ExternalFailure );
}
}
@@ -158,18 +158,18 @@ void MPEG_MetaHandler::CacheFileData()
this->parent->fileRef = 0;
}
- this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), this->xmpPacket.size() );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
this->containsXMP = true;
}
-} // MPEG_MetaHandler::CacheFileData
+} // MPEG2_MetaHandler::CacheFileData
// =================================================================================================
-// MPEG_MetaHandler::UpdateFile
-// ============================
+// MPEG2_MetaHandler::UpdateFile
+// =============================
-void MPEG_MetaHandler::UpdateFile ( bool doSafeUpdate )
+void MPEG2_MetaHandler::UpdateFile ( bool doSafeUpdate )
{
if ( ! this->needsUpdate ) return;
@@ -177,7 +177,7 @@ void MPEG_MetaHandler::UpdateFile ( bool doSafeUpdate )
XMP_Assert ( fileRef != 0 );
XMP_StringPtr packetStr = this->xmpPacket.c_str();
- XMP_StringLen packetLen = this->xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
if ( ! doSafeUpdate ) {
@@ -218,17 +218,17 @@ void MPEG_MetaHandler::UpdateFile ( bool doSafeUpdate )
this->needsUpdate = false;
-} // MPEG_MetaHandler::UpdateFile
+} // MPEG2_MetaHandler::UpdateFile
// =================================================================================================
-// MPEG_MetaHandler::WriteFile
-// ===========================
+// MPEG2_MetaHandler::WriteFile
+// ============================
-void MPEG_MetaHandler::WriteFile ( LFA_FileRef sourceRef,
+void MPEG2_MetaHandler::WriteFile ( LFA_FileRef sourceRef,
const std::string & sourcePath )
{
IgnoreParam(sourceRef); IgnoreParam(sourcePath);
- XMP_Throw ( "MPEG_MetaHandler::WriteFile: Should never be called", kXMPErr_Unavailable );
+ XMP_Throw ( "MPEG2_MetaHandler::WriteFile: Should never be called", kXMPErr_Unavailable );
-} // MPEG_MetaHandler::WriteFile
+} // MPEG2_MetaHandler::WriteFile
diff --git a/source/XMPFiles/FileHandlers/MPEG_Handler.hpp b/source/XMPFiles/FileHandlers/MPEG2_Handler.hpp
index 21484ce..8bb0464 100644
--- a/source/XMPFiles/FileHandlers/MPEG_Handler.hpp
+++ b/source/XMPFiles/FileHandlers/MPEG2_Handler.hpp
@@ -1,5 +1,5 @@
-#ifndef __MPEG_Handler_hpp__
-#define __MPEG_Handler_hpp__ 1
+#ifndef __MPEG2_Handler_hpp__
+#define __MPEG2_Handler_hpp__ 1
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
@@ -13,45 +13,45 @@
#include "XMPFiles_Impl.hpp"
// =================================================================================================
-/// \file MPEG_Handler.hpp
-/// \brief File format handler for MPEG.
+/// \file MPEG2_Handler.hpp
+/// \brief File format handler for MPEG2.
///
/// This header ...
///
// =================================================================================================
-extern XMPFileHandler * MPEG_MetaHandlerCTor ( XMPFiles * parent );
+extern XMPFileHandler * MPEG2_MetaHandlerCTor ( XMPFiles * parent );
-extern bool MPEG_CheckFormat ( XMP_FileFormat format,
+extern bool MPEG2_CheckFormat ( XMP_FileFormat format,
XMP_StringPtr filePath,
LFA_FileRef fileRef,
XMPFiles * parent);
-static const XMP_OptionBits kMPEG_HandlerFlags = ( kXMPFiles_CanInjectXMP |
- kXMPFiles_CanExpand |
- kXMPFiles_CanRewrite |
- kXMPFiles_AllowsOnlyXMP |
- kXMPFiles_ReturnsRawPacket |
- kXMPFiles_HandlerOwnsFile |
- kXMPFiles_AllowsSafeUpdate |
- kXMPFiles_UsesSidecarXMP );
+static const XMP_OptionBits kMPEG2_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_UsesSidecarXMP );
-class MPEG_MetaHandler : public XMPFileHandler
+class MPEG2_MetaHandler : public XMPFileHandler
{
public:
std::string sidecarPath;
- MPEG_MetaHandler ( XMPFiles * parent );
- ~MPEG_MetaHandler();
+ MPEG2_MetaHandler ( XMPFiles * parent );
+ ~MPEG2_MetaHandler();
void CacheFileData();
void UpdateFile ( bool doSafeUpdate );
void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath );
-}; // MPEG_MetaHandler
+}; // MPEG2_MetaHandler
// =================================================================================================
-#endif /* __MPEG_Handler_hpp__ */
+#endif /* __MPEG2_Handler_hpp__ */
diff --git a/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp b/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp
new file mode 100644
index 0000000..a3f92d6
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/MPEG4_Handler.cpp
@@ -0,0 +1,909 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2007 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "MPEG4_Handler.hpp"
+
+#include "UnicodeConversions.hpp"
+#include "MD5.h"
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+using namespace std;
+
+// =================================================================================================
+/// \file MPEG4_Handler.cpp
+/// \brief File format handler for MPEG-4, a flavor of the ISO Base Media File Format.
+///
+/// This handler ...
+///
+// =================================================================================================
+
+// File and box type codes as big endian 32-bit integers, allows faster comparisons.
+
+static XMP_Uns32 kBE_ftyp = MakeUns32BE ( 0x66747970UL ); // File header Box, no version/flags.
+
+static XMP_Uns32 kBE_mp41 = MakeUns32BE ( 0x6D703431UL ); // Compatibility codes
+static XMP_Uns32 kBE_mp42 = MakeUns32BE ( 0x6D703432UL );
+static XMP_Uns32 kBE_f4v = MakeUns32BE ( 0x66347620UL );
+
+static XMP_Uns32 kBE_moov = MakeUns32BE ( 0x6D6F6F76UL ); // Container Box, no version/flags.
+static XMP_Uns32 kBE_mvhd = MakeUns32BE ( 0x6D766864UL ); // Data FullBox, has version/flags.
+static XMP_Uns32 kBE_udta = MakeUns32BE ( 0x75647461UL ); // Container Box, no version/flags.
+static XMP_Uns32 kBE_cprt = MakeUns32BE ( 0x63707274UL ); // Data FullBox, has version/flags.
+static XMP_Uns32 kBE_uuid = MakeUns32BE ( 0x75756964UL ); // Data Box, no version/flags.
+static XMP_Uns32 kBE_free = MakeUns32BE ( 0x66726565UL ); // Free space Box, no version/flags.
+static XMP_Uns32 kBE_mdat = MakeUns32BE ( 0x6D646174UL ); // Media data Box, no version/flags.
+
+static XMP_Uns32 kBE_xmpUUID [4] = { MakeUns32BE ( 0xBE7ACFCBUL ),
+ MakeUns32BE ( 0x97A942E8UL ),
+ MakeUns32BE ( 0x9C719994UL ),
+ MakeUns32BE ( 0x91E3AFACUL ) };
+
+// ! The buffer and constants are both already big endian.
+#define Get4CharCode(buffPtr) (*((XMP_Uns32*)(buffPtr)))
+
+// =================================================================================================
+
+// Pairs of 3 letter ISO 639-2 codes mapped to 2 letter ISO 639-1 codes from:
+// http://www.loc.gov/standards/iso639-2/php/code_list.php
+// Would have to write an "==" operator to use std::map, must compare values not pointers.
+// ! Not fully sorted, do not use a binary search.
+static XMP_StringPtr kKnownLangs[] =
+ { "aar", "aa", "abk", "ab", "afr", "af", "aka", "ak", "alb", "sq", "sqi", "sq", "amh", "am",
+ "ara", "ar", "arg", "an", "arm", "hy", "hye", "hy", "asm", "as", "ava", "av", "ave", "ae",
+ "aym", "ay", "aze", "az", "bak", "ba", "bam", "bm", "baq", "eu", "eus", "eu", "bel", "be",
+ "ben", "bn", "bih", "bh", "bis", "bi", "bod", "bo", "tib", "bo", "bos", "bs", "bre", "br",
+ "bul", "bg", "bur", "my", "mya", "my", "cat", "ca", "ces", "cs", "cze", "cs", "cha", "ch",
+ "che", "ce", "chi", "zh", "zho", "zh", "chu", "cu", "chv", "cv", "cor", "kw", "cos", "co",
+ "cre", "cr", "cym", "cy", "wel", "cy", "cze", "cs", "ces", "cs", "dan", "da", "deu", "de",
+ "ger", "de", "div", "dv", "dut", "nl", "nld", "nl", "dzo", "dz", "ell", "el", "gre", "el",
+ "eng", "en", "epo", "eo", "est", "et", "eus", "eu", "baq", "eu", "ewe", "ee", "fao", "fo",
+ "fas", "fa", "per", "fa", "fij", "fj", "fin", "fi", "fra", "fr", "fre", "fr", "fre", "fr",
+ "fra", "fr", "fry", "fy", "ful", "ff", "geo", "ka", "kat", "ka", "ger", "de", "deu", "de",
+ "gla", "gd", "gle", "ga", "glg", "gl", "glv", "gv", "gre", "el", "ell", "el", "grn", "gn",
+ "guj", "gu", "hat", "ht", "hau", "ha", "heb", "he", "her", "hz", "hin", "hi", "hmo", "ho",
+ "hrv", "hr", "scr", "hr", "hun", "hu", "hye", "hy", "arm", "hy", "ibo", "ig", "ice", "is",
+ "isl", "is", "ido", "io", "iii", "ii", "iku", "iu", "ile", "ie", "ina", "ia", "ind", "id",
+ "ipk", "ik", "isl", "is", "ice", "is", "ita", "it", "jav", "jv", "jpn", "ja", "kal", "kl",
+ "kan", "kn", "kas", "ks", "kat", "ka", "geo", "ka", "kau", "kr", "kaz", "kk", "khm", "km",
+ "kik", "ki", "kin", "rw", "kir", "ky", "kom", "kv", "kon", "kg", "kor", "ko", "kua", "kj",
+ "kur", "ku", "lao", "lo", "lat", "la", "lav", "lv", "lim", "li", "lin", "ln", "lit", "lt",
+ "ltz", "lb", "lub", "lu", "lug", "lg", "mac", "mk", "mkd", "mk", "mah", "mh", "mal", "ml",
+ "mao", "mi", "mri", "mi", "mar", "mr", "may", "ms", "msa", "ms", "mkd", "mk", "mac", "mk",
+ "mlg", "mg", "mlt", "mt", "mol", "mo", "mon", "mn", "mri", "mi", "mao", "mi", "msa", "ms",
+ "may", "ms", "mya", "my", "bur", "my", "nau", "na", "nav", "nv", "nbl", "nr", "nde", "nd",
+ "ndo", "ng", "nep", "ne", "nld", "nl", "dut", "nl", "nno", "nn", "nob", "nb", "nor", "no",
+ "nya", "ny", "oci", "oc", "oji", "oj", "ori", "or", "orm", "om", "oss", "os", "pan", "pa",
+ "per", "fa", "fas", "fa", "pli", "pi", "pol", "pl", "por", "pt", "pus", "ps", "que", "qu",
+ "roh", "rm", "ron", "ro", "rum", "ro", "rum", "ro", "ron", "ro", "run", "rn", "rus", "ru",
+ "sag", "sg", "san", "sa", "scc", "sr", "srp", "sr", "scr", "hr", "hrv", "hr", "sin", "si",
+ "slk", "sk", "slo", "sk", "slo", "sk", "slk", "sk", "slv", "sl", "sme", "se", "smo", "sm",
+ "sna", "sn", "snd", "sd", "som", "so", "sot", "st", "spa", "es", "sqi", "sq", "alb", "sq",
+ "srd", "sc", "srp", "sr", "scc", "sr", "ssw", "ss", "sun", "su", "swa", "sw", "swe", "sv",
+ "tah", "ty", "tam", "ta", "tat", "tt", "tel", "te", "tgk", "tg", "tgl", "tl", "tha", "th",
+ "tib", "bo", "bod", "bo", "tir", "ti", "ton", "to", "tsn", "tn", "tso", "ts", "tuk", "tk",
+ "tur", "tr", "twi", "tw", "uig", "ug", "ukr", "uk", "urd", "ur", "uzb", "uz", "ven", "ve",
+ "vie", "vi", "vol", "vo", "wel", "cy", "cym", "cy", "wln", "wa", "wol", "wo", "xho", "xh",
+ "yid", "yi", "yor", "yo", "zha", "za", "zho", "zh", "chi", "zh", "zul", "zu",
+ 0, 0 };
+
+static XMP_StringPtr Lookup2LetterLang ( XMP_StringPtr lang3 )
+{
+
+ for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) {
+ if ( XMP_LitMatch ( lang3, kKnownLangs[i] ) ) return kKnownLangs[i+1];
+ }
+
+ return lang3; // Not found, return the input.
+
+} // Lookup2LetterLang
+
+// =================================================================================================
+// MPEG4_CheckFormat
+// =================
+//
+// An MPEG-4 file is an instance of an ISO Base Media file, ISO 14496-12 and -14. An MPEG-4 file
+// must begin with an 'ftyp' box containing 'mp41', 'mp42', or 'f4v ' in the compatible brands.
+//
+// The 'ftyp' box structure is:
+//
+// 0 4 uns32 box size, 0 means "to EoF"
+// 4 4 uns32 box type, 'ftyp'
+// 8 8 uns64 box size, if uns32 size is 1
+// - 4 uns32 major brand
+// - 4 uns32 minor version
+// - * uns32 sequence of compatible brands, to the end of the box
+//
+// All integers are big endian.
+
+bool MPEG4_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles* parent )
+{
+ XMP_Uns8 buffer [4096];
+ XMP_Uns32 ioCount, brandCount, brandOffset, id;
+ XMP_Uns64 fileSize, boxSize;
+
+ // Do some basic header checks, figure out how many compatible brands are listed.
+
+ fileSize = LFA_Measure ( fileRef );
+
+ LFA_Seek ( fileRef, 0, SEEK_SET );
+ ioCount = LFA_Read ( fileRef, buffer, 16 ); // Read to the compatible brands, assuming a 32-bit size.
+ if ( ioCount < 16 ) return false;
+
+ id = Get4CharCode ( &buffer[4] ); // Is the first box an 'ftyp' box?
+ if ( id != kBE_ftyp ) return false;
+
+ boxSize = GetUns32BE ( &buffer[0] ); // Get the box length.
+ brandOffset = 16;
+
+ if ( boxSize == 0 ) {
+ boxSize = fileSize;
+ } else if ( boxSize == 1 ) {
+ boxSize = GetUns64BE ( &buffer[8] );
+ LFA_Seek ( fileRef, 24, SEEK_SET ); // Seek to the compatible brands.
+ brandOffset = 24;
+ }
+
+ if ( (boxSize < brandOffset) || (boxSize > fileSize) ||
+ ((boxSize & 0x3) != 0) || (boxSize > 4024) ) return false; // Sanity limit of 1000 brands.
+
+ // Look for the 'mp41', 'mp42', of 'f4v ' compatible brands.
+
+ brandCount = (XMP_Uns32)( (boxSize - brandOffset) / 4 );
+ while ( brandCount > 0 ) {
+
+ XMP_Uns32 clumpSize = brandCount * 4;
+ if ( clumpSize > sizeof(buffer) ) clumpSize = sizeof(buffer);
+ ioCount = LFA_Read ( fileRef, buffer, clumpSize );
+ if ( ioCount < clumpSize ) return false;
+
+ for ( XMP_Uns32 i = 0; i < clumpSize; i += 4 ) {
+ id = Get4CharCode ( &buffer[i] );
+ if ( (id == kBE_mp41) || (id == kBE_mp42) || (id == kBE_f4v) ) return true;
+ }
+
+ brandCount -= clumpSize/4;
+
+ }
+
+ return false;
+
+} // MPEG4_CheckFormat
+
+// =================================================================================================
+// MPEG4_MetaHandlerCTor
+// =====================
+
+XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent )
+{
+
+ return new MPEG4_MetaHandler ( parent );
+
+} // MPEG4_MetaHandlerCTor
+
+// =================================================================================================
+// MPEG4_MetaHandler::MPEG4_MetaHandler
+// ====================================
+
+MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent ) : xmpBoxPos(0)
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kMPEG4_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+} // MPEG4_MetaHandler::MPEG4_MetaHandler
+
+// =================================================================================================
+// MPEG4_MetaHandler::~MPEG4_MetaHandler
+// =====================================
+
+MPEG4_MetaHandler::~MPEG4_MetaHandler()
+{
+
+ // Nothing to do yet.
+
+} // MPEG4_MetaHandler::~MPEG4_MetaHandler
+
+// =================================================================================================
+// GetBoxInfo
+// ==========
+//
+// Seek to the start of a box and extract the type and size, split the size into header and content
+// portions. Leave the file positioned at the first byte of content.
+
+// ! We're returning the content size, not the raw (full) MPEG-4 box size!
+
+static void GetBoxInfo ( LFA_FileRef fileRef, XMP_Uns64 fileSize, XMP_Uns64 boxPos,
+ XMP_Uns32 * boxType, XMP_Uns64 * headerSize, XMP_Uns64 * contentSize )
+{
+ XMP_Uns8 buffer [8];
+ XMP_Uns32 u32Size;
+
+ LFA_Seek ( fileRef, boxPos, SEEK_SET );
+ (void) LFA_Read ( fileRef, buffer, 8, kLFA_RequireAll );
+
+ u32Size = GetUns32BE ( &buffer[0] );
+ *boxType = Get4CharCode ( &buffer[4] );
+
+ if ( u32Size > 1 ) {
+ *headerSize = 8; // Normal explicit size case.
+ *contentSize = u32Size - 8;
+ } else if ( u32Size == 0 ) {
+ *headerSize = 8; // The box goes to EoF.
+ *contentSize = fileSize - (boxPos + 8);
+ } else {
+ XMP_Uns64 u64Size; // Have a 64-bit size.
+ (void) LFA_Read ( fileRef, &u64Size, 8, kLFA_RequireAll );
+ u64Size = MakeUns64BE ( u64Size );
+ *headerSize = 16;
+ *contentSize = u64Size - 16;
+ }
+
+} // GetBoxInfo
+
+// =================================================================================================
+// MPEG4_MetaHandler::CacheFileData
+// ================================
+//
+// The XMP in MPEG-4 is in a top level XMP 'uuid' box. Legacy metadata might be found in 2 places,
+// the 'moov'/'mvhd' box, and the 'moov'/'udta'/'cprt' boxes. There is at most 1 each of the 'moov',
+// 'mvhd' and 'udta' boxes. There are any number of 'cprt' boxes, including none. The 'udta' box can
+// be large, so don't cache all of it.
+
+void MPEG4_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) );
+
+ LFA_FileRef fileRef = this->parent->fileRef;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+ XMP_Uns64 outerPos, outerSize, hSize, cSize;
+ XMP_Uns32 boxType;
+
+ // The outer loop looks for the top level 'moov' and 'uuid'/XMP boxes.
+
+ bool moovFound = false;
+ if ( this->parent->openFlags & kXMPFiles_OpenOnlyXMP ) moovFound = true; // Ignore legacy.
+
+ for ( outerPos = 0; (outerPos < fileSize) && ((! this->containsXMP) || (! moovFound)); outerPos += outerSize ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
+ }
+
+ GetBoxInfo ( fileRef, fileSize, outerPos, &boxType, &hSize, &cSize );
+ outerSize = hSize + cSize;
+
+ if ( (! this->containsXMP) && (boxType == kBE_uuid) ) {
+
+ XMP_Uns8 uuid [16];
+ LFA_Read ( fileRef, uuid, 16, kLFA_RequireAll );
+
+ if ( memcmp ( uuid, kBE_xmpUUID, 16 ) == 0 ) {
+
+ // Found the XMP, record the offset and size, read the packet.
+
+ this->containsXMP = true;
+ this->xmpBoxPos = outerPos;
+ this->packetInfo.offset = outerPos + hSize + 16;
+ this->packetInfo.length = (XMP_Int32) (cSize - 16);
+
+ this->xmpPacket.reserve ( this->packetInfo.length );
+ this->xmpPacket.assign ( this->packetInfo.length, ' ' );
+ LFA_Read ( fileRef, (void*)this->xmpPacket.data(), this->packetInfo.length, kLFA_RequireAll );
+
+ }
+
+ } else if ( (! moovFound) && (boxType == kBE_moov) ) {
+
+ // The middle loop loop looks for the 'moov'/'mvhd' and 'moov'/'udta' boxes.
+
+ moovFound = true;
+
+ XMP_Uns64 middleStart = outerPos + hSize;
+ XMP_Uns64 middleEnd = outerPos + outerSize;
+ XMP_Uns64 middlePos, middleSize;
+
+ bool mvhdFound = false, udtaFound = false;
+
+ for ( middlePos = middleStart; (middlePos < middleEnd) && ((! mvhdFound) || (! udtaFound)); middlePos += middleSize ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
+ }
+
+ GetBoxInfo ( fileRef, fileSize, middlePos, &boxType, &hSize, &cSize );
+ middleSize = hSize + cSize;
+
+ if ( (! mvhdFound) && (boxType == kBE_mvhd) ) {
+
+ // Save the entire 'moov'/'mvhd' box content, it isn't very big.
+ mvhdFound = true;
+ this->mvhdBox.reserve ( (size_t)cSize );
+ this->mvhdBox.assign ( (size_t)cSize, ' ' );
+ LFA_Read ( fileRef, (void*)this->mvhdBox.data(), (XMP_Int32)cSize, kLFA_RequireAll );
+
+ } else if ( (! udtaFound) && (boxType == kBE_udta) ) {
+
+ // The inner loop looks for the 'moov'/'udta'/'cprt' boxes.
+
+ udtaFound = true;
+ XMP_Uns64 innerStart = middlePos + hSize;
+ XMP_Uns64 innerEnd = middlePos + middleSize;
+ XMP_Uns64 innerPos, innerSize;
+
+ for ( innerPos = innerStart; innerPos < innerEnd; innerPos += innerSize ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
+ }
+
+ GetBoxInfo ( fileRef, fileSize, innerPos, &boxType, &hSize, &cSize );
+ innerSize = hSize + cSize;
+ if ( boxType != kBE_cprt ) continue;
+
+ // ! Actually capturing structured data - the 'cprt' box content.
+ this->cprtBoxes.push_back ( std::string() );
+ std::string & newCprt = this->cprtBoxes.back();
+ newCprt.reserve ( (size_t)cSize );
+ newCprt.assign ( (size_t)cSize, ' ' );
+ LFA_Read ( fileRef, (void*)newCprt.data(), (XMP_Int32)cSize, kLFA_RequireAll );
+
+ } // inner loop
+
+ } // 'moov'/'udta' box
+
+ } // middle loop
+
+ } // 'moov' box
+
+ } // outer loop
+
+} // MPEG4_MetaHandler::CacheFileData
+
+// =================================================================================================
+// MPEG4_MetaHandler::MakeLegacyDigest
+// ===================================
+
+// *** Will need updating if we process the 'ilst' metadata.
+
+#define kHexDigits "0123456789ABCDEF"
+
+void MPEG4_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ MD5_CTX context;
+ unsigned char digestBin [16];
+ MD5Init ( &context );
+
+ MD5Update ( &context, (XMP_Uns8*)this->mvhdBox.c_str(), (unsigned int) this->mvhdBox.size() );
+
+ for ( size_t i = 0, limit = this->cprtBoxes.size(); i < limit; ++i ) {
+ const std::string & currCprt = this->cprtBoxes[i];
+ MD5Update ( &context, (XMP_Uns8*)currCprt.c_str(), (unsigned int) currCprt.size() );
+ }
+
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->erase();
+ digestStr->append ( buffer, 32 );
+
+} // MPEG4_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+
+struct MVHD_v1 { // v1 v0 - offsets within the content portion of the 'mvhd' box
+ XMP_Uns8 version; // 0 0
+ XMP_Uns8 flags [3]; // 1 1
+ XMP_Uns64 creationTime; // 4 4 - Uns32 in v0
+ XMP_Uns64 modificationTime; // 12 8 - Uns32 in v0
+ XMP_Uns32 timescale; // 20 12
+ XMP_Uns64 duration; // 24 16 - Uns32 in v0
+ XMP_Int32 rate; // 32 20
+ XMP_Int16 volume; // 36 24
+ XMP_Uns16 pad_1; // 38 26
+ XMP_Uns32 pad_2, pad_3; // 40 28
+ XMP_Int32 matrix [9]; // 48 36
+ XMP_Uns32 preDef [6]; // 84 72
+ XMP_Uns32 nextTrackID; // 108 96
+}; // 112 100
+
+static void ExtractMVHD_v0 ( XMP_Uns8 * buffer, MVHD_v1 * mvhd ) // Always convert to the v1 form.
+{
+ mvhd->version = buffer[0];
+ mvhd->flags[0] = buffer[1];
+ mvhd->flags[1] = buffer[2];
+ mvhd->flags[2] = buffer[3];
+ mvhd->creationTime = GetUns32BE ( &buffer[ 4] );
+ mvhd->modificationTime = GetUns32BE ( &buffer[ 8] );
+ mvhd->timescale = GetUns32BE ( &buffer[12] );
+ mvhd->duration = GetUns32BE ( &buffer[16] );
+ mvhd->rate = GetUns32BE ( &buffer[20] );
+ mvhd->volume = GetUns16BE ( &buffer[24] );
+ mvhd->pad_1 = GetUns16BE ( &buffer[26] );
+ mvhd->pad_2 = GetUns32BE ( &buffer[28] );
+ mvhd->pad_3 = GetUns32BE ( &buffer[32] );
+ for ( int i = 0, j = 36; i < 9; ++i, j += 4 ) mvhd->matrix[i] = GetUns32BE ( &buffer[j] );
+ for ( int i = 0, j = 72; i < 6; ++i, j += 4 ) mvhd->preDef[i] = GetUns32BE ( &buffer[j] );
+ mvhd->nextTrackID = GetUns32BE ( &buffer[96] );
+}
+
+static void ExtractMVHD_v1 ( XMP_Uns8 * buffer, MVHD_v1 * mvhd )
+{
+ mvhd->version = buffer[0];
+ mvhd->flags[0] = buffer[1];
+ mvhd->flags[1] = buffer[2];
+ mvhd->flags[2] = buffer[3];
+ mvhd->creationTime = GetUns64BE ( &buffer[ 4] );
+ mvhd->modificationTime = GetUns64BE ( &buffer[12] );
+ mvhd->timescale = GetUns32BE ( &buffer[20] );
+ mvhd->duration = GetUns64BE ( &buffer[24] );
+ mvhd->rate = GetUns32BE ( &buffer[32] );
+ mvhd->volume = GetUns16BE ( &buffer[36] );
+ mvhd->pad_1 = GetUns16BE ( &buffer[38] );
+ mvhd->pad_2 = GetUns32BE ( &buffer[40] );
+ mvhd->pad_3 = GetUns32BE ( &buffer[44] );
+ for ( int i = 0, j = 48; i < 9; ++i, j += 4 ) mvhd->matrix[i] = GetUns32BE ( &buffer[j] );
+ for ( int i = 0, j = 84; i < 6; ++i, j += 4 ) mvhd->preDef[i] = GetUns32BE ( &buffer[j] );
+ mvhd->nextTrackID = GetUns32BE ( &buffer[108] );
+}
+
+// =================================================================================================
+// MPEG4_MetaHandler::ProcessXMP
+// =============================
+
+#define kAlmostMaxSeconds 0x7FFFFF00
+
+void MPEG4_MetaHandler::ProcessXMP()
+{
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ if ( this->mvhdBox.empty() && this->cprtBoxes.empty() ) return; // No legacy, we're done.
+
+ std::string oldDigest;
+ bool oldDigestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "MPEG-4", &oldDigest, 0 );
+
+ if ( oldDigestFound ) {
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) return; // No legacy changes.
+ }
+
+ // If we get here we need to import the legacy metadata. Either there is no old digest in the
+ // XMP, or the digests differ. In the former case keep any existing XMP, in the latter case take
+ // new legacy values. So, oldDigestFound means digestsDiffer, else we would have returned.
+
+ // *** The "official" MPEG-4 metadata might not be very interesting. It looks like the 'ilst'
+ // *** metadata (invented by Apple?) is more interesting. There appears to be no official
+ // *** documentation.
+
+ if ( ! this->mvhdBox.empty() ) {
+
+ MVHD_v1 mvhd;
+ XMP_DateTime xmpDate;
+
+ if ( this->mvhdBox.size() == 100 ) { // *** Should check the version - extract to a static function.
+ ExtractMVHD_v0 ( (XMP_Uns8*)(this->mvhdBox.data()), &mvhd );
+ } else if ( this->mvhdBox.size() == 112 ) {
+ ExtractMVHD_v1 ( (XMP_Uns8*)(this->mvhdBox.data()), &mvhd );
+ }
+
+ if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "CreateDate" )) ) {
+ if ( (mvhd.creationTime >> 32) < 0xFF ) { // Sanity check for bogus date info.
+
+ memset ( &xmpDate, 0, sizeof(xmpDate) ); // AUDIT: Using sizeof(xmpDate) is safe.
+ xmpDate.year = 1904; // Start at midnight, January 1 1904, UTC
+ xmpDate.month = 1; // ! Note that the XMP binary fields are signed to allow
+ xmpDate.day = 1; // ! offsets and normalization in both directions.
+
+ while ( mvhd.creationTime > kAlmostMaxSeconds ) {
+ xmpDate.second += kAlmostMaxSeconds;
+ SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect.
+ mvhd.creationTime -= kAlmostMaxSeconds;
+ }
+ xmpDate.second += (XMP_Uns32)mvhd.creationTime;
+ SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect.
+
+ this->xmpObj.SetProperty_Date ( kXMP_NS_XMP, "CreateDate", xmpDate );
+ this->containsXMP = true;
+
+ }
+ }
+
+ if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "ModifyDate" )) ) {
+ if ( (mvhd.modificationTime >> 32) < 0xFF ) { // Sanity check for bogus date info.
+
+ memset ( &xmpDate, 0, sizeof(xmpDate) ); // AUDIT: Using sizeof(xmpDate) is safe.
+ xmpDate.year = 1904; // Start at midnight, January 1 1904, UTC
+ xmpDate.month = 1;
+ xmpDate.day = 1;
+
+ while ( mvhd.modificationTime > kAlmostMaxSeconds ) {
+ xmpDate.second += kAlmostMaxSeconds;
+ SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect.
+ mvhd.modificationTime -= kAlmostMaxSeconds;
+ }
+ xmpDate.second += (XMP_Uns32)mvhd.modificationTime;
+ SXMPUtils::ConvertToUTCTime ( &xmpDate ); // ! For the normalization side effect.
+
+ this->xmpObj.SetProperty_Date ( kXMP_NS_XMP, "ModifyDate", xmpDate );
+ this->containsXMP = true;
+
+ }
+ }
+
+ if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) {
+ if ( mvhd.timescale != 0 ) { // Avoid 1/0 for the scale field.
+ char buffer [32]; // A 64-bit number is at most 20 digits.
+ this->xmpObj.DeleteProperty ( kXMP_NS_DM, "duration" ); // Delete the whole struct.
+ snprintf ( buffer, sizeof(buffer), "%llu", mvhd.duration ); // AUDIT: The buffer is big enough.
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", &buffer[0] );
+ snprintf ( buffer, sizeof(buffer), "1/%u", mvhd.timescale ); // AUDIT: The buffer is big enough.
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", &buffer[0] );
+ this->containsXMP = true;
+ }
+ }
+
+ }
+
+ if ( oldDigestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "rights" )) ) {
+
+ std::string tempStr;
+ char lang3 [4]; // The unpacked ISO-639-2/T language code.
+ lang3[3] = 0;
+
+ for ( size_t i = 0, limit = this->cprtBoxes.size(); i < limit; ++i ) {
+ if ( this->cprtBoxes[i].size() < 7 ) continue;
+
+ const XMP_Uns8 * currCprt = (XMP_Uns8*) (this->cprtBoxes[i].c_str()); // ! Actually structured data!
+ size_t rawLen = this->cprtBoxes[i].size() - 6;
+ if ( currCprt[0] != 0 ) continue; // Only proceed for version 0, ignore the flags.
+
+ XMP_Uns16 packedLang = GetUns16BE ( &currCprt[4] );
+ lang3[0] = (packedLang >> 10) | 0x60;
+ lang3[1] = ((packedLang >> 5) & 0x1F) | 0x60;
+ lang3[2] = (packedLang & 0x1F) | 0x60;
+
+ XMP_StringPtr xmpLang = Lookup2LetterLang ( lang3 );
+ XMP_StringPtr xmpValue = (XMP_StringPtr) &currCprt[6];
+
+ if ( (rawLen >= 8) && (GetUns16BE ( xmpValue ) == 0xFEFF) ) {
+ FromUTF16 ( (UTF16Unit*)xmpValue, rawLen/2, &tempStr, true /* big endian */ );
+ xmpValue = tempStr.c_str();
+ }
+
+ this->xmpObj.SetLocalizedText ( kXMP_NS_DC, "rights", xmpLang, "", xmpValue );
+ this->containsXMP = true;
+
+ }
+
+ }
+
+} // MPEG4_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// MPEG4_MetaHandler::PickNewLocation
+// ==================================
+//
+// Pick a new location for the XMP. This is the first available 'free' space before any 'mdat' box,
+// otherwise the end of the file. Any existing XMP 'uuid' box has already been marked as 'free'.
+
+void MPEG4_MetaHandler::PickNewLocation()
+{
+ LFA_FileRef fileRef = this->parent->fileRef;
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+
+ XMP_Uns32 xmpBoxSize = 4+4+16 + (XMP_Uns32)this->xmpPacket.size();
+
+ XMP_Uns64 currPos, prevPos; // Info about the most recent 2 boxes.
+ XMP_Uns32 currType, prevType;
+ XMP_Uns64 currSize, prevSize, hSize, cSize;
+
+ XMP_Uns32 be32Size;
+ XMP_Uns64 be64Size;
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ bool pastMDAT = false;
+
+ currType = 0;
+ prevPos = prevSize = currSize = 0;
+ for ( currPos = 0; currPos < fileSize; currPos += currSize ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ XMP_Throw ( "MPEG4_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort );
+ }
+
+ prevPos += prevSize; // ! We'll go through at least once, the first box is 'ftyp'.
+ prevType = currType; // ! Care is needed to preserve the prevBox and currBox info when
+ prevSize = currSize; // ! the loop exits because it hits EoF.
+ XMP_Assert ( (prevPos + prevSize) == currPos );
+
+ GetBoxInfo ( fileRef, fileSize, currPos, &currType, &hSize, &cSize );
+ currSize = hSize + cSize;
+
+ if ( pastMDAT ) continue; // Keep scanning to the end of the file.
+ if ( currType == kBE_mdat ) pastMDAT = true;
+ if ( currType != kBE_free ) continue;
+ if ( currSize >= xmpBoxSize ) break;
+
+ if ( prevType == kBE_free ) {
+ // If we get here the prevBox and currBox are both 'free' and neither big enough alone.
+ // Pretend to combine them for this check and for possible following checks. We don't
+ // really compbine them, we just remember the start and total length.
+ currPos = prevPos;
+ currSize += prevSize;
+ prevSize = 0; // ! For the start of the next loop pass.
+ if ( currSize >= xmpBoxSize ) break;
+ }
+
+ }
+
+ if ( currPos < fileSize ) {
+
+ // Put the XMP at the start of the 'free' space. Increase the size of the XMP if the excess
+ // is less than 8 bytes, otherwise write a new 'free' box header.
+
+ this->xmpBoxPos = currPos;
+ XMP_Assert ( (currType == kBE_free) && (currSize >= xmpBoxSize) );
+
+ XMP_Uns64 excessSpace = currSize - xmpBoxSize;
+ if ( excessSpace < 8 ) {
+ this->xmpPacket.append ( (size_t)excessSpace, ' ' );
+ } else {
+ LFA_Seek ( fileRef, (currPos + xmpBoxSize), SEEK_SET );
+ if ( excessSpace <= 0xFFFFFFFFULL ) {
+ be32Size = MakeUns32BE ( (XMP_Uns32)excessSpace );
+ LFA_Write ( fileRef, &be32Size, 4 );
+ LFA_Write ( fileRef, &kBE_free, 4 );
+ } else {
+ be32Size = MakeUns32BE ( 1 );
+ be64Size = MakeUns64BE ( excessSpace );
+ LFA_Write ( fileRef, &be32Size, 4 );
+ LFA_Write ( fileRef, &kBE_free, 4 );
+ LFA_Write ( fileRef, &be64Size, 8 );
+ }
+ }
+
+ } else {
+
+ // Appending the XMP, make sure the current final box has an explicit size.
+
+ this->xmpBoxPos = fileSize;
+ XMP_Assert ( currPos == fileSize );
+
+ currPos -= currSize; // Move back to the final box's origin.
+ LFA_Seek ( fileRef, currPos, SEEK_SET );
+ LFA_Read ( fileRef, &be32Size, 4, kLFA_RequireAll );
+ be32Size = MakeUns32BE ( be32Size );
+
+ if ( be32Size == 0 ) {
+
+ // The current final box is "to-EoF", we need to write the actual size. If the size fits
+ // in 32 bits then we just set it in the leading Uns32 field instead of the "to-EoF"
+ // value. Otherwise we have to insert a 64-bit size. If the previous box is 'free' then
+ // we take 8 bytes from the end of it, shift the size/type parts of the final box up 8
+ // bytes, making space for the 64-bit size. Give up if the previous box is not 'free'.
+
+ if ( currSize <= 0xFFFFFFFFULL ) {
+
+ // The size fits in 32 bits, reuse the leading Uns32 size.
+ be32Size = MakeUns32BE ( (XMP_Uns32)currSize );
+ LFA_Seek ( fileRef, currPos, SEEK_SET );
+ LFA_Write ( fileRef, &be32Size, 4 );
+
+ } else if ( prevType != kBE_free ) {
+
+ // We need to insert a 64-bit size, but the previous box is not 'free'.
+ XMP_Throw ( "MPEG4_MetaHandler::PickNewLocation - Can't set box size", kXMPErr_ExternalFailure );
+
+ } else if ( prevSize == 8 ) {
+
+ // Absorb the whole free box.
+ LFA_Seek ( fileRef, prevPos, SEEK_SET );
+ be32Size = MakeUns32BE ( 1 );
+ LFA_Write ( fileRef, &be32Size, 4 );
+ LFA_Write ( fileRef, &currType, 4 );
+ be64Size = MakeUns64BE ( currSize );
+ LFA_Write ( fileRef, &be64Size, 8 );
+
+ } else {
+
+ // Trim 8 bytes off the end of the free box.
+
+ prevSize -= 8;
+
+ LFA_Seek ( fileRef, prevPos, SEEK_SET );
+ LFA_Read ( fileRef, &be32Size, 4, kLFA_RequireAll );
+ if ( be32Size != MakeUns32BE ( 1 ) ) {
+ be32Size = MakeUns32BE ( (XMP_Uns32)prevSize );
+ LFA_Seek ( fileRef, prevPos, SEEK_SET );
+ LFA_Write ( fileRef, &be32Size, 4 );
+ } else {
+ be64Size = MakeUns64BE ( prevSize );
+ LFA_Seek ( fileRef, (prevPos + 8), SEEK_SET );
+ LFA_Write ( fileRef, &be64Size, 8 );
+ }
+
+ LFA_Seek ( fileRef, (currPos - 8), SEEK_SET );
+ be32Size = MakeUns32BE ( 1 );
+ LFA_Write ( fileRef, &be32Size, 4 );
+ LFA_Write ( fileRef, &currType, 4 );
+ be64Size = MakeUns64BE ( currSize );
+ LFA_Write ( fileRef, &be64Size, 8 );
+
+ }
+
+ }
+
+ }
+
+} // MPEG4_MetaHandler::PickNewLocation
+
+// =================================================================================================
+// MPEG4_MetaHandler::UpdateFile
+// =============================
+
+// *** There are no writebacks to legacy metadata yet. We need to resolve the questions about
+// *** standard MPEG-4 metadata versus 'ilst' metadata.
+
+// *** The current logic for the XMP is simple. Use the existing XMP if it is big enough, next look
+// *** for 'free' space before any 'mdat' boxes, finally append to the end.
+
+// ! MPEG-4 can be indexed with absolute offsets, only the 'moov' and XMP 'uuid' boxes can be moved!
+
+void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+ XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates.
+
+ XMP_AbortProc abortProc = this->parent->abortProc;
+ void * abortArg = this->parent->abortArg;
+ const bool checkAbort = (abortProc != 0);
+
+ LFA_FileRef fileRef = this->parent->fileRef;
+ XMP_Uns64 fileSize = LFA_Measure ( fileRef );
+
+ XMP_Uns32 be32Size;
+
+ // Make sure the XMP has a current legacy digest.
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "MPEG-4", newDigest.c_str(), kXMP_DeleteExisting );
+ XMP_StringLen xmpLen = (XMP_StringLen)this->xmpPacket.size();
+ try {
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength), xmpLen );
+ } catch ( ... ) {
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
+ }
+
+ #if XMP_DebugBuild // Sanity check that the XMP is where we think it is.
+
+ if ( this->xmpBoxPos != 0 ) {
+
+ XMP_Uns32 boxType;
+ XMP_Uns64 hSize, cSize;
+ XMP_Uns8 uuid [16];
+
+ GetBoxInfo ( fileRef, LFA_Measure ( fileRef ), this->xmpBoxPos, &boxType, &hSize, &cSize );
+ LFA_Read ( fileRef, uuid, 16, kLFA_RequireAll );
+
+ if ( ((this->xmpBoxPos + hSize + 16) != (XMP_Uns64)this->packetInfo.offset) ||
+ ((cSize - 16) != (XMP_Uns64)this->packetInfo.length) ||
+ (boxType != kBE_uuid) || (memcmp ( uuid, kBE_xmpUUID, 16 ) != 0) ) {
+ XMP_Throw ( "Inaccurate MPEG-4 packet info", kXMPErr_InternalFailure );
+ }
+
+ }
+
+ #endif
+
+ if ( (this->xmpBoxPos != 0) && (this->xmpPacket.size() <= (size_t)this->packetInfo.length) ) {
+
+ // Update existing XMP in-place.
+
+ if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) {
+ // They ought to match, cheap to be sure.
+ size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size();
+ this->xmpPacket.append ( extraSpace, ' ' );
+ }
+
+ XMP_Assert ( this->xmpPacket.size() == (size_t)this->packetInfo.length );
+ LFA_Seek ( fileRef, this->packetInfo.offset, SEEK_SET );
+ LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() );
+
+ } else if ( (this->xmpBoxPos != 0) &&
+ ((XMP_Uns64)(this->packetInfo.offset + this->packetInfo.length) == fileSize) ) {
+
+ // The XMP is already at the end of the file, rewrite the whole 'uuid' box.
+
+ XMP_Assert ( this->xmpPacket.size() > (size_t)this->packetInfo.length );
+
+ LFA_Seek ( fileRef, this->xmpBoxPos, SEEK_SET );
+ be32Size = MakeUns32BE ( 4+4+16 + (XMP_Uns32)this->xmpPacket.size() ); // ! XMP must be 4GB or less!
+ LFA_Write ( fileRef, &be32Size, 4 );
+ LFA_Write ( fileRef, &kBE_uuid, 4 );
+ LFA_Write ( fileRef, &kBE_xmpUUID, 16 );
+ LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() );
+
+ } else {
+
+ // We are injecting first time XMP, or making the existing XMP larger. Pick a new location
+ // for the XMP. This is the first available space before any 'mdat' box, otherwise the end
+ // of the file. Available space can be any contiguous combination of 'free' space with the
+ // existing XMP. Mark any existing XMP as 'free' first, this simplifies the logic and we
+ // can't do it later since we might reuse the space.
+
+ if ( this->xmpBoxPos != 0 ) {
+ LFA_Seek ( fileRef, (this->xmpBoxPos + 4), SEEK_SET );
+ LFA_Write ( fileRef, "free", 4 );
+ }
+
+ this->PickNewLocation(); // ! Might increase the size of the XMP packet.
+
+ LFA_Seek ( fileRef, this->xmpBoxPos, SEEK_SET );
+ be32Size = MakeUns32BE ( 4+4+16 + (XMP_Uns32)this->xmpPacket.size() ); // ! XMP must be 4GB or less!
+ LFA_Write ( fileRef, &be32Size, 4 );
+ LFA_Write ( fileRef, &kBE_uuid, 4 );
+ LFA_Write ( fileRef, &kBE_xmpUUID, 16 );
+ LFA_Write ( fileRef, this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() );
+
+ }
+
+} // MPEG4_MetaHandler::UpdateFile
+
+// =================================================================================================
+// MPEG4_MetaHandler::WriteFile
+// ============================
+//
+// Since the XMP and legacy is probably a miniscule part of the entire file, and since we can't
+// change the offset of most of the boxes, just copy the entire source file to the dest file, then
+// do an in-place update to the destination file.
+
+void MPEG4_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+ XMP_Assert ( this->needsUpdate );
+
+ LFA_FileRef destRef = this->parent->fileRef;
+
+ LFA_Seek ( sourceRef, 0, SEEK_SET );
+ LFA_Seek ( destRef, 0, SEEK_SET );
+ LFA_Copy ( sourceRef, destRef, LFA_Measure ( sourceRef ),
+ this->parent->abortProc, this->parent->abortArg );
+
+ this->UpdateFile ( false );
+
+} // MPEG4_MetaHandler::WriteFile
+
+// =================================================================================================
diff --git a/source/XMPFiles/FileHandlers/MPEG4_Handler.hpp b/source/XMPFiles/FileHandlers/MPEG4_Handler.hpp
new file mode 100644
index 0000000..9da2741
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/MPEG4_Handler.hpp
@@ -0,0 +1,69 @@
+#ifndef __MPEG4_Handler_hpp__
+#define __MPEG4_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2007 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMPFiles_Impl.hpp"
+
+// ================================================================================================
+/// \file MPEG4_Handler.hpp
+/// \brief File format handler for MPEG-4.
+///
+/// This header ...
+///
+// ================================================================================================
+
+extern XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool MPEG4_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kMPEG4_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_AllowsSafeUpdate
+ );
+
+class MPEG4_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath );
+
+ MPEG4_MetaHandler ( XMPFiles * _parent );
+ virtual ~MPEG4_MetaHandler();
+
+private:
+
+ MPEG4_MetaHandler() : xmpBoxPos(0) {}; // Hidden on purpose.
+
+ void MakeLegacyDigest ( std::string * digestStr );
+ void PickNewLocation();
+
+ XMP_Uns64 xmpBoxPos; // The file offset of the XMP box (the size field, not the content).
+
+ std::string mvhdBox; // ! Both contain binary data, but std::string is handy.
+ std::vector<std::string> cprtBoxes;
+
+}; // MPEG4_MetaHandler
+
+// =================================================================================================
+
+#endif // __MPEG4_Handler_hpp__
diff --git a/source/XMPFiles/FileHandlers/P2_Handler.cpp b/source/XMPFiles/FileHandlers/P2_Handler.cpp
new file mode 100644
index 0000000..eac0edf
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/P2_Handler.cpp
@@ -0,0 +1,1203 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "P2_Handler.hpp"
+
+#include "MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file P2_Handler.cpp
+/// \brief Folder format handler for P2.
+///
+/// This handler is for the P2 video format. This is a pseudo-package, visible files but with a very
+/// well-defined layout and naming rules. A typical P2 example looks like:
+///
+/// .../MyMovie
+/// CONTENTS/
+/// CLIP/
+/// 0001AB.XML
+/// 0001AB.XMP
+/// 0002CD.XML
+/// 0002CD.XMP
+/// VIDEO/
+/// 0001AB.MXF
+/// 0002CD.MXF
+/// AUDIO/
+/// 0001AB00.MXF
+/// 0001AB01.MXF
+/// 0002CD00.MXF
+/// 0002CD01.MXF
+/// ICON/
+/// 0001AB.BMP
+/// 0002CD.BMP
+/// VOICE/
+/// 0001AB.WAV
+/// 0002CD.WAV
+/// PROXY/
+/// 0001AB.MP4
+/// 0002CD.MP4
+///
+/// From the user's point of view, .../MyMovie contains P2 stuff, in this case 2 clips whose raw
+/// names are 0001AB and 0002CD. There may be mapping information for nicer clip names to the raw
+/// names, but that can be ignored for now. Each clip is stored as a collection of files, each file
+/// holding some specific aspect of the clip's data.
+///
+/// The P2 handler operates on clips. The path from the client of XMPFiles can be either a logical
+/// clip path, like ".../MyMovie/0001AB", or a full path to one of the files. In the latter case the
+/// handler must figure out the intended clip, it must not blindly use the named file.
+///
+/// Once the P2 structure and intended clip are identified, the handler only deals with the .XMP and
+/// .XML files in the CLIP folder. The .XMP file, if present, contains the XMP for the clip. The .XML
+/// file must be present to define the existance of the clip. It contains a variety of information
+/// about the clip, including some legacy metadata.
+///
+// =================================================================================================
+
+static const char * kContentFolderNames[] = { "CLIP", "VIDEO", "AUDIO", "ICON", "VOICE", "PROXY", 0 };
+static int kNumRequiredContentFolders = 6; // All 6 of the above.
+
+static inline bool CheckContentFolderName ( const std::string & folderName )
+{
+ for ( int i = 0; kContentFolderNames[i] != 0; ++i ) {
+ if ( folderName == kContentFolderNames[i] ) return true;
+ }
+ return false;
+}
+
+// =================================================================================================
+// InternalMakeClipFilePath
+// ========================
+//
+// P2_CheckFormat can't use the member function.
+
+static void InternalMakeClipFilePath ( std::string * path,
+ const std::string & rootPath,
+ const std::string & clipName,
+ XMP_StringPtr suffix )
+{
+
+ *path = rootPath;
+ *path += kDirChar;
+ *path += "CONTENTS";
+ *path += kDirChar;
+ *path += "CLIP";
+ *path += kDirChar;
+ *path += clipName;
+ *path += suffix;
+
+} // InternalMakeClipFilePath
+
+// =================================================================================================
+// P2_CheckFormat
+// ==============
+//
+// This version does fairly simple checks. The top level folder (.../MyMovie) must a child folder
+// called CONTENTS. This must have a subfolder called CLIP. It may also have subfolders called
+// VIDEO, AUDIO, ICON, VOICE, and PROXY. Any mixture of these additional folders is allowed, but no
+// other children are allowed in CONTENTS. The CLIP folder must contain a .XML file for the desired
+// clip. The name checks are case insensitive.
+//
+// The state of the string parameters depends on the form of the path passed by the client. If the
+// client passed a logical clip path, like ".../MyMovie/0001AB", the parameters are:
+// rootPath - ".../MyMovie"
+// gpName - empty
+// parentName - empty
+// leafName - "0001AB"
+// If the client passed a full file path, like ".../MyMovie/CONTENTS/VOICE/0001AB.WAV", they are:
+// rootPath - ".../MyMovie"
+// gpName - "CONTENTS"
+// parentName - "VOICE"
+// leafName - "0001AB"
+//
+// For most of the content files the base file name is the raw clip name. Files in the AUDIO and
+// VOICE folders have an extra 2 digits appended to the raw clip name. These must be trimmed.
+
+// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has
+// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
+// ! file exists for a full file path.
+
+bool P2_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ XMP_FolderInfo folderInfo;
+ std::string tempPath, childName;
+
+ std::string clipName = leafName;
+
+ // Do some basic checks on the gpName and parentName.
+
+ if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty.
+
+ if ( ! gpName.empty() ) {
+
+ if ( gpName != "CONTENTS" ) return false;
+ if ( ! CheckContentFolderName ( parentName ) ) return false;
+
+ if ( (parentName == "AUDIO") | (parentName == "VOICE") ) {
+ if ( clipName.size() < 3 ) return false;
+ clipName.erase ( clipName.size() - 2 );
+ }
+
+ }
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "CONTENTS";
+ if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFolder ) return false;
+
+ folderInfo.Open ( tempPath.c_str() );
+ int numChildrenFound = 0;
+ while ( ( folderInfo.GetNextChild ( &childName ) && ( numChildrenFound < kNumRequiredContentFolders ) ) ) { // Make sure the children of CONTENTS are legit.
+ if ( CheckContentFolderName ( childName ) ) {
+ folderInfo.GetFolderPath ( &tempPath );
+ tempPath += kDirChar;
+ tempPath += childName;
+ if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFolder ) return false;
+ ++numChildrenFound;
+ }
+ }
+ folderInfo.Close();
+
+ // Make sure the clip's .XML file exists.
+
+ InternalMakeClipFilePath ( &tempPath, rootPath, clipName, ".XML" );
+ if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false;
+
+ // Make a bogus path to pass the root path and clip name to the handler. A bit of a hack, but
+ // the only way to get info from here to there.
+
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += clipName;
+
+ size_t pathLen = tempPath.size() + 1; // Include a terminating nul.
+ parent->handlerTemp = malloc ( pathLen );
+ if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for P2 clip path", kXMPErr_NoMemory );
+ memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above.
+
+ return true;
+
+} // P2_CheckFormat
+
+// =================================================================================================
+// P2_MetaHandlerCTor
+// ==================
+
+XMPFileHandler * P2_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new P2_MetaHandler ( parent );
+
+} // P2_MetaHandlerCTor
+
+// =================================================================================================
+// P2_MetaHandler::P2_MetaHandler
+// ==============================
+
+P2_MetaHandler::P2_MetaHandler ( XMPFiles * _parent ) : expat(0), clipMetadata(0), clipContent(0)
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kP2_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ // Extract the root path and clip name from handlerTemp.
+
+ XMP_Assert ( this->parent->handlerTemp != 0 );
+ this->rootPath = (char*)this->parent->handlerTemp;
+ free ( this->parent->handlerTemp );
+ this->parent->handlerTemp = 0;
+
+ SplitLeafName ( &this->rootPath, &this->clipName );
+
+} // P2_MetaHandler::P2_MetaHandler
+
+// =================================================================================================
+// P2_MetaHandler::~P2_MetaHandler
+// ===============================
+
+P2_MetaHandler::~P2_MetaHandler()
+{
+
+ this->CleanupLegacyXML();
+ if ( this->parent->handlerTemp != 0 ) {
+ free ( this->parent->handlerTemp );
+ this->parent->handlerTemp = 0;
+ }
+
+} // P2_MetaHandler::~P2_MetaHandler
+
+// =================================================================================================
+// P2_MetaHandler::MakeClipFilePath
+// ================================
+
+void P2_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix )
+{
+
+ InternalMakeClipFilePath ( path, this->rootPath, this->clipName, suffix );
+
+} // P2_MetaHandler::MakeClipFilePath
+
+// =================================================================================================
+// P2_MetaHandler::CleanupLegacyXML
+// ================================
+
+void P2_MetaHandler::CleanupLegacyXML()
+{
+
+ if ( ! this->defaultNS.empty() ) {
+ SXMPMeta::DeleteNamespace ( this->defaultNS.c_str() );
+ this->defaultNS.erase();
+ }
+
+ if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; }
+
+ clipMetadata = 0; // ! Was a pointer into the expat tree.
+ clipContent = 0; // ! Was a pointer into the expat tree.
+
+} // P2_MetaHandler::CleanupLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::DigestLegacyItem
+// ================================
+
+void P2_MetaHandler::DigestLegacyItem ( MD5_CTX & md5Context, XML_NodePtr legacyContext, XMP_StringPtr legacyPropName )
+{
+ XML_NodePtr legacyProp = legacyContext->GetNamedElement ( this->p2NS.c_str(), legacyPropName );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &md5Context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+} // P2_MetaHandler::DigestLegacyItem
+
+// =================================================================================================
+// P2_MetaHandler::DigestLegacyRelations
+// =====================================
+
+void P2_MetaHandler::DigestLegacyRelations ( MD5_CTX & md5Context )
+{
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_Node * legacyContext = this->clipContent->GetNamedElement ( p2NS, "Relation" );
+
+ if ( legacyContext != 0 ) {
+
+ this->DigestLegacyItem ( md5Context, legacyContext, "GlobalShotID" );
+ XML_Node * legacyConnectionContext = legacyContext = this->clipContent->GetNamedElement ( p2NS, "Connection" );
+
+ if ( legacyConnectionContext != 0 ) {
+
+ legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Top" );
+
+ if ( legacyContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" );
+ }
+
+ legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Previous" );
+
+ if ( legacyContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" );
+ }
+
+ legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Next" );
+
+ if ( legacyContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" );
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::DigestLegacyRelations
+
+// =================================================================================================
+// P2_MetaHandler::SetXMPPropertyFromLegacyXML
+// ===========================================
+
+void P2_MetaHandler::SetXMPPropertyFromLegacyXML ( bool digestFound,
+ XML_NodePtr legacyContext,
+ XMP_StringPtr schemaNS,
+ XMP_StringPtr propName,
+ XMP_StringPtr legacyPropName,
+ bool isLocalized )
+{
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( schemaNS, propName )) ) {
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyProp = legacyContext->GetNamedElement ( p2NS, legacyPropName );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ if ( isLocalized ) {
+ this->xmpObj.SetLocalizedText ( schemaNS, propName, "", "x-default", legacyProp->GetLeafContentValue(), kXMP_DeleteExisting );
+ } else {
+ this->xmpObj.SetProperty ( schemaNS, propName, legacyProp->GetLeafContentValue(), kXMP_DeleteExisting );
+ }
+ this->containsXMP = true;
+ }
+
+ }
+
+} // P2_MetaHandler::SetXMPPropertyFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetRelationsFromLegacyXML
+// =========================================
+
+void P2_MetaHandler::SetRelationsFromLegacyXML ( bool digestFound )
+{
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyRelationContext = this->clipContent->GetNamedElement ( p2NS, "Relation" );
+
+ // P2 Relation blocks are optional -- they're only present when a clip is part of a multi-clip shot.
+
+ if ( legacyRelationContext != 0 ) {
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "relation" )) ) {
+
+ XML_NodePtr legacyProp = legacyRelationContext->GetNamedElement ( p2NS, "GlobalShotID" );
+ std::string relationString;
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ this->xmpObj.DeleteProperty ( kXMP_NS_DC, "relation" );
+ relationString = std::string("globalShotID:") + legacyProp->GetLeafContentValue();
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString );
+ this->containsXMP = true;
+
+ XML_NodePtr legacyConnectionContext = legacyRelationContext->GetNamedElement ( p2NS, "Connection" );
+
+ if ( legacyConnectionContext != 0 ) {
+
+ XML_NodePtr legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Top" );
+
+ if ( legacyContext != 0 ) {
+ legacyProp = legacyContext->GetNamedElement ( p2NS, "GlobalClipID" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ relationString = std::string("topGlobalClipID:") + legacyProp->GetLeafContentValue();
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString );
+ }
+ }
+
+ legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Previous" );
+
+ if ( legacyContext != 0 ) {
+ legacyProp = legacyContext->GetNamedElement ( p2NS, "GlobalClipID" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ relationString = std::string("previousGlobalClipID:") + legacyProp->GetLeafContentValue();
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString );
+ }
+ }
+
+ legacyContext = legacyConnectionContext->GetNamedElement ( p2NS, "Next" );
+
+ if ( legacyContext != 0 ) {
+ legacyProp = legacyContext->GetNamedElement ( p2NS, "GlobalClipID" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ relationString = std::string("nextGlobalClipID:") + legacyProp->GetLeafContentValue();
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString );
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetRelationsFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetAudioInfoFromLegacyXML
+// =========================================
+
+void P2_MetaHandler::SetAudioInfoFromLegacyXML ( bool digestFound )
+{
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyAudioContext = this->clipContent->GetNamedElement ( p2NS, "EssenceList" );
+
+ if ( legacyAudioContext != 0 ) {
+
+ legacyAudioContext = legacyAudioContext->GetNamedElement ( p2NS, "Audio" );
+
+ if ( legacyAudioContext != 0 ) {
+
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyAudioContext, kXMP_NS_DM, "audioSampleRate", "SamplingRate", false );
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "audioSampleType" )) ) {
+ XML_NodePtr legacyProp = legacyAudioContext->GetNamedElement ( p2NS, "BitsPerSample" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ const std::string p2BitsPerSample = legacyProp->GetLeafContentValue();
+ std::string dmSampleType;
+
+ if ( p2BitsPerSample == "16" ) {
+ dmSampleType = "16Int";
+ } else if ( p2BitsPerSample == "24" ) {
+ dmSampleType = "32Int";
+ }
+
+ if ( ! dmSampleType.empty() ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "audioSampleType", dmSampleType, kXMP_DeleteExisting );
+ this->containsXMP = true;
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetAudioInfoFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetVideoInfoFromLegacyXML
+// =========================================
+
+void P2_MetaHandler::SetVideoInfoFromLegacyXML ( bool digestFound )
+{
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyVideoContext = this->clipContent->GetNamedElement ( p2NS, "EssenceList" );
+
+ if ( legacyVideoContext != 0 ) {
+
+ legacyVideoContext = legacyVideoContext->GetNamedElement ( p2NS, "Video" );
+
+ if ( legacyVideoContext != 0 ) {
+ this->SetVideoFrameInfoFromLegacyXML ( legacyVideoContext, digestFound );
+ this->SetStartTimecodeFromLegacyXML ( legacyVideoContext, digestFound );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyVideoContext, kXMP_NS_DM, "videoFrameRate", "FrameRate", false );
+ }
+
+ }
+
+} // P2_MetaHandler::SetVideoInfoFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetDurationFromLegacyXML
+// ========================================
+
+void P2_MetaHandler::SetDurationFromLegacyXML ( bool digestFound )
+{
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) {
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyDurationProp = this->clipContent->GetNamedElement ( p2NS, "Duration" );
+ XML_NodePtr legacyEditUnitProp = this->clipContent->GetNamedElement ( p2NS, "EditUnit" );
+
+ if ( (legacyDurationProp != 0) && ( legacyEditUnitProp != 0 ) &&
+ legacyDurationProp->IsLeafContentNode() && legacyEditUnitProp->IsLeafContentNode() ) {
+
+ this->xmpObj.DeleteProperty ( kXMP_NS_DM, "duration" );
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "duration",
+ kXMP_NS_DM, "value", legacyDurationProp->GetLeafContentValue() );
+
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "duration",
+ kXMP_NS_DM, "scale", legacyEditUnitProp->GetLeafContentValue() );
+ this->containsXMP = true;
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetDurationFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetVideoFrameInfoFromLegacyXML
+// ==============================================
+
+void P2_MetaHandler::SetVideoFrameInfoFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound )
+{
+
+ // Map the P2 Codec field to various dynamic media schema fields.
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "videoFrameSize" )) ) {
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "Codec" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ const std::string p2Codec = legacyProp->GetLeafContentValue();
+ std::string dmPixelAspectRatio, dmVideoCompressor, dmWidth, dmHeight;
+
+ if ( p2Codec == "DV25_411" ) {
+ dmWidth = "720";
+ dmVideoCompressor = "DV25 4:1:1";
+ } else if ( p2Codec == "DV25_420" ) {
+ dmWidth = "720";
+ dmVideoCompressor = "DV25 4:2:0";
+ } else if ( p2Codec == "DV50_422" ) {
+ dmWidth = "720";
+ dmVideoCompressor = "DV50 4:2:2";
+ } else if ( ( p2Codec == "DV100_1080/59.94i" ) || ( p2Codec == "DV100_1080/50i" ) ) {
+ dmVideoCompressor = "DV100";
+ dmHeight = "1080";
+
+ if ( p2Codec == "DV100_1080/59.94i" ) {
+ dmWidth = "1280";
+ dmPixelAspectRatio = "3/2";
+ } else {
+ dmWidth = "1440";
+ dmPixelAspectRatio = "1920/1440";
+ }
+ } else if ( ( p2Codec == "DV100_720/59.94p" ) || ( p2Codec == "DV100_720/50p" ) ) {
+ dmVideoCompressor = "DV100";
+ dmHeight = "720";
+ dmWidth = "960";
+ dmPixelAspectRatio = "1920/1440";
+ } else if ( ( p2Codec == "AVC-I_1080/59.94i" ) ||
+ ( p2Codec == "AVC-I_1080/50i" ) ||
+ ( p2Codec == "AVC-I_1080/29.97p" ) ||
+ ( p2Codec == "AVC-I_1080/25p" ) ||
+ ( p2Codec == "AVC-I_720/59.94p" ) ||
+ ( p2Codec == "AVC-I_720/50p" ) ) {
+ // There are two "flavors" of the AVC-Intra codec that compress to different widths (and, therefore
+ // different pixel aspect ratios), but no way I can find to distinguish between them in the
+ // legacy XML. Until this is resolved we'll just report the codec name.
+ dmVideoCompressor = "AVC-Intra";
+ }
+
+ if ( dmWidth == "720" ) {
+
+ // This is SD footage -- calculate the frame height and pixel aspect ratio using the legacy P2
+ // FrameRate and AspectRatio fields.
+
+ legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "FrameRate" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ const std::string p2FrameRate = legacyProp->GetLeafContentValue();
+
+ legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "AspectRatio" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ const std::string p2AspectRatio = legacyProp->GetLeafContentValue();
+
+ if ( p2FrameRate == "50i" ) {
+ // Standard Definition PAL.
+ dmHeight = "576";
+ if ( p2AspectRatio == "4:3" ) {
+ dmPixelAspectRatio = "768/702";
+ } else if ( p2AspectRatio == "16:9" ) {
+ dmPixelAspectRatio = "1024/702";
+ }
+ } else if ( p2FrameRate == "59.94i" ) {
+ // Standard Definition NTSC.
+ dmHeight = "480";
+ if ( p2AspectRatio == "4:3" ) {
+ dmPixelAspectRatio = "10/11";
+ } else if ( p2AspectRatio == "16:9" ) {
+ dmPixelAspectRatio = "40/33";
+ }
+ }
+
+ }
+ }
+ }
+
+ if ( ! dmPixelAspectRatio.empty() ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "videoPixelAspectRatio", dmPixelAspectRatio, kXMP_DeleteExisting );
+ this->containsXMP = true;
+ }
+
+ if ( ! dmVideoCompressor.empty() ) {
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "videoCompressor", dmVideoCompressor, kXMP_DeleteExisting );
+ this->containsXMP = true;
+ }
+
+ if ( ( ! dmWidth.empty() ) && ( ! dmHeight.empty() ) ) {
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", dmWidth, 0 );
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", dmHeight, 0 );
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", "pixel", 0 );
+ this->containsXMP = true;
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetVideoFrameInfoFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::SetStartTimecodeFromLegacyXML
+// =============================================
+
+void P2_MetaHandler::SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound )
+{
+
+ // Translate start timecode to the format specified by the dynamic media schema.
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "startTimecode" )) ) {
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "StartTimecode" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ std::string p2StartTimecode = legacyProp->GetLeafContentValue();
+
+ legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "FrameRate" );
+
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+
+ const std::string p2FrameRate = legacyProp->GetLeafContentValue();
+ const XMP_StringPtr p2DropFrameFlag = legacyProp->GetAttrValue ( "DropFrameFlag" );
+ std::string dmTimeFormat;
+
+ if ( ( p2FrameRate == "50i" ) || ( p2FrameRate == "25p" ) ) {
+
+ dmTimeFormat = "25Timecode";
+
+ } else if ( p2FrameRate == "23.98p" ) {
+
+ dmTimeFormat = "23976Timecode";
+
+ } else if ( p2FrameRate == "50p" ) {
+
+ dmTimeFormat = "50Timecode";
+
+ } else if ( p2FrameRate == "59.94p" ) {
+
+ if ( p2DropFrameFlag == "true" ) {
+ dmTimeFormat = "5994DropTimecode";
+ } else if ( p2DropFrameFlag == "false" ) {
+ dmTimeFormat = "5994NonDropTimecode";
+ }
+
+ } else if ( ( p2FrameRate == "59.94i" ) || ( p2FrameRate == "29.97p" ) ) {
+
+ if ( p2DropFrameFlag != 0 ) {
+
+ if ( std::strcmp ( p2DropFrameFlag, "false" ) == 0 ) {
+
+ dmTimeFormat = "2997NonDropTimecode";
+
+ } else if ( std::strcmp ( p2DropFrameFlag, "true" ) == 0 ) {
+
+ // Drop frame NTSC timecode uses semicolons instead of colons as separators.
+ std::string::iterator currCharIt = p2StartTimecode.begin();
+ const std::string::iterator charsEndIt = p2StartTimecode.end();
+
+ for ( ; currCharIt != charsEndIt; ++currCharIt ) {
+ if ( *currCharIt == ':' ) *currCharIt = ';';
+ }
+
+ dmTimeFormat = "2997DropTimecode";
+
+ }
+
+ }
+
+ }
+
+ if ( ( ! p2StartTimecode.empty() ) && ( ! dmTimeFormat.empty() ) ) {
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", p2StartTimecode, 0 );
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", dmTimeFormat, 0 );
+ this->containsXMP = true;
+ }
+
+ }
+
+ }
+
+ }
+
+} // P2_MetaHandler::SetStartTimecodeFromLegacyXML
+
+// =================================================================================================
+// P2_MetaHandler::ForceChildElement
+// =================================
+
+XML_Node * P2_MetaHandler::ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, int indent /* = 0 */ )
+{
+ XML_Node * wsNode;
+ XML_Node * childNode = parent->GetNamedElement ( this->p2NS.c_str(), localName );
+
+ if ( childNode == 0 ) {
+
+ // The indenting is a hack, assuming existing 2 spaces per level.
+
+ wsNode = new XML_Node ( parent, "", kCDataNode );
+ wsNode->value = " "; // Add 2 spaces to the existing WS before the parent's close tag.
+ parent->content.push_back ( wsNode );
+
+ childNode = new XML_Node ( parent, localName, kElemNode );
+ childNode->ns = parent->ns;
+ childNode->nsPrefixLen = parent->nsPrefixLen;
+ childNode->name.insert ( 0, parent->name, 0, parent->nsPrefixLen );
+ parent->content.push_back ( childNode );
+
+ wsNode = new XML_Node ( parent, "", kCDataNode );
+ wsNode->value = '\n';
+ for ( ; indent > 1; --indent ) wsNode->value += " "; // Indent less 1, to "outdent" the parent's close.
+ parent->content.push_back ( wsNode );
+
+ }
+
+ return childNode;
+
+} // P2_MetaHandler::ForceChildElement
+
+// =================================================================================================
+// P2_MetaHandler::MakeLegacyDigest
+// =================================
+
+// *** Early hack version.
+
+#define kHexDigits "0123456789ABCDEF"
+
+void P2_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ digestStr->erase();
+ if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML.
+ XMP_Assert ( this->expat != 0 );
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyContext;
+ MD5_CTX md5Context;
+ unsigned char digestBin [16];
+ MD5Init ( &md5Context );
+
+ legacyContext = this->clipContent;
+ this->DigestLegacyItem ( md5Context, legacyContext, "ClipName" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "GlobalClipID" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "Duration" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "EditUnit" );
+ this->DigestLegacyRelations ( md5Context );
+
+ legacyContext = this->clipContent->GetNamedElement ( p2NS, "EssenceList" );
+
+ if ( legacyContext != 0 ) {
+
+ XML_NodePtr videoContext = legacyContext->GetNamedElement ( p2NS, "Video" );
+
+ if ( videoContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, videoContext, "AspectRatio" );
+ this->DigestLegacyItem ( md5Context, videoContext, "Codec" );
+ this->DigestLegacyItem ( md5Context, videoContext, "FrameRate" );
+ this->DigestLegacyItem ( md5Context, videoContext, "StartTimecode" );
+ }
+
+ XML_NodePtr audioContext = legacyContext->GetNamedElement ( p2NS, "Audio" );
+
+ if ( audioContext != 0 ) {
+ this->DigestLegacyItem ( md5Context, audioContext, "SamplingRate" );
+ this->DigestLegacyItem ( md5Context, audioContext, "BitsPerSample" );
+ }
+
+ }
+
+ legacyContext = this->clipMetadata;
+ this->DigestLegacyItem ( md5Context, legacyContext, "UserClipName" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "ShotMark" );
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Access" );
+ if ( legacyContext == 0 ) return;
+
+ this->DigestLegacyItem ( md5Context, legacyContext, "Creator" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "CreationDate" );
+ this->DigestLegacyItem ( md5Context, legacyContext, "LastUpdateDate" );
+
+ MD5Final ( digestBin, &md5Context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->append ( buffer );
+
+} // P2_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// P2_MetaHandler::CacheFileData
+// =============================
+
+void P2_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) );
+
+ // Make sure the clip's .XMP file exists.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, ".XMP" );
+
+ if ( GetFileMode ( xmpPath.c_str() ) != kFMode_IsFile ) return; // No XMP.
+
+ // Read the entire .XMP file.
+
+ bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate );
+ char openMode = 'r';
+ if ( openForUpdate ) openMode = 'w';
+
+ LFA_FileRef xmpFile = LFA_Open ( xmpPath.c_str(), openMode );
+ if ( xmpFile == 0 ) return; // The open failed.
+
+ XMP_Int64 xmpLen = LFA_Measure ( xmpFile );
+ if ( xmpLen > 100*1024*1024 ) {
+ XMP_Throw ( "P2 XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check.
+ }
+
+ this->xmpPacket.erase();
+ this->xmpPacket.reserve ( (size_t)xmpLen );
+ this->xmpPacket.append ( (size_t)xmpLen, ' ' );
+
+ XMP_Int32 ioCount = LFA_Read ( xmpFile, (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen, kLFA_RequireAll );
+ XMP_Assert ( ioCount == xmpLen );
+
+ this->packetInfo.offset = 0;
+ this->packetInfo.length = (XMP_Int32)xmpLen;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ XMP_Assert ( this->parent->fileRef == 0 );
+ if ( openMode == 'r' ) {
+ LFA_Close ( xmpFile );
+ } else {
+ this->parent->fileRef = xmpFile;
+ }
+
+ this->containsXMP = true;
+
+} // P2_MetaHandler::CacheFileData
+
+// =================================================================================================
+// P2_MetaHandler::ProcessXMP
+// ==========================
+
+void P2_MetaHandler::ProcessXMP()
+{
+
+ // Some versions of gcc can't tolerate goto's across declarations.
+ // *** Better yet, avoid this cruft with self-cleaning objects.
+ #define CleanupAndExit \
+ { \
+ bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \
+ if ( ! openForUpdate ) this->CleanupLegacyXML(); \
+ return; \
+ }
+
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ // --------------------------------------------------------------
+ // *** This is a minimal Q&D example of legacy metadata handling.
+ // *** Hack: Special case trickery to detect and clean up default XML namespace usage.
+
+ bool haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", 0 ); // Is there already a default namespace?
+
+ std::string xmlPath;
+ this->MakeClipFilePath ( &xmlPath, ".XML" );
+
+ AutoFile xmlFile;
+ xmlFile.fileRef = LFA_Open ( xmlPath.c_str(), 'r' );
+ if ( xmlFile.fileRef == 0 ) return; // The open failed.
+
+ this->expat = XMP_NewExpatAdapter();
+ if ( this->expat == 0 ) XMP_Throw ( "P2_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory );
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = LFA_Read ( xmlFile.fileRef, buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+ this->expat->ParseBuffer ( 0, 0, true ); // End the parse.
+
+ LFA_Close ( xmlFile.fileRef );
+ xmlFile.fileRef = 0;
+
+ if ( ! haveDefaultNS ) {
+ // No prior default XML namespace. If there is one now, remember it and delete it when done.
+ haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", &this->defaultNS );
+ XMP_Assert ( haveDefaultNS == (! this->defaultNS.empty()) );
+ }
+
+ // The root element should be P2Main in some namespace. At least 2 different namespaces are in
+ // use (ending in "v3.0" and "v3.1"). Take whatever this file uses.
+
+ XML_Node & xmlTree = this->expat->tree;
+ XML_NodePtr rootElem = 0;
+
+ for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) {
+ if ( xmlTree.content[i]->kind == kElemNode ) {
+ rootElem = xmlTree.content[i];
+ }
+ }
+
+ if ( rootElem == 0 ) CleanupAndExit
+ XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rootLocalName, "P2Main" ) ) CleanupAndExit
+
+ this->p2NS = rootElem->ns;
+
+ // Now find ClipMetadata element and check the legacy digest.
+
+ XMP_StringPtr p2NS = this->p2NS.c_str();
+ XML_NodePtr legacyContext, legacyProp;
+
+ legacyContext = rootElem->GetNamedElement ( p2NS, "ClipContent" );
+ if ( legacyContext == 0 ) CleanupAndExit
+
+ this->clipContent = legacyContext; // ! Save the ClipContext pointer for other use.
+
+ legacyContext = legacyContext->GetNamedElement ( p2NS, "ClipMetadata" );
+ if ( legacyContext == 0 ) CleanupAndExit
+
+ this->clipMetadata = legacyContext; // ! Save the ClipMetadata pointer for other use.
+
+ std::string oldDigest, newDigest;
+ bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "P2", &oldDigest, 0 );
+ if ( digestFound ) {
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) CleanupAndExit
+ }
+
+ // If we get here we need find and import the actual legacy elements using the current namespace.
+ // Either there is no old digest in the XMP, or the digests differ. In the former case keep any
+ // existing XMP, in the latter case take new legacy values.
+ this->SetXMPPropertyFromLegacyXML ( digestFound, this->clipContent, kXMP_NS_DC, "title", "ClipName", true );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, this->clipContent, kXMP_NS_DC, "identifier", "GlobalClipID", false );
+ this->SetDurationFromLegacyXML (digestFound );
+ this->SetRelationsFromLegacyXML ( digestFound );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, this->clipMetadata, kXMP_NS_DM, "shotName", "UserClipName", false );
+ this->SetAudioInfoFromLegacyXML ( digestFound );
+ this->SetVideoInfoFromLegacyXML ( digestFound );
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Access" );
+ if ( legacyContext == 0 ) CleanupAndExit
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "creator" )) ) {
+ legacyProp = legacyContext->GetNamedElement ( p2NS, "Creator" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ this->xmpObj.DeleteProperty ( kXMP_NS_DC, "creator" );
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsUnordered,
+ legacyProp->GetLeafContentValue() );
+ this->containsXMP = true;
+ }
+ }
+
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_XMP, "CreateDate", "CreationDate", false );
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_XMP, "ModifyDate", "LastUpdateDate", false );
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_XMP, "Rating" )) ) {
+ legacyProp = legacyContext->GetNamedElement ( p2NS, "ShotMark" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) {
+ XMP_StringPtr markValue = legacyProp->GetLeafContentValue();
+
+ // Translate "marked" clips as having a rating of 1 and "unmarked" clips as having a rating of 0
+ if ((markValue == 0) || (strcmp(markValue, "false") == 0) || (strcmp(markValue, "0") == 0)) {
+ this->xmpObj.SetProperty ( kXMP_NS_XMP, "Rating", "0", kXMP_DeleteExisting );
+ this->containsXMP = true;
+ } else if ((strcmp(markValue, "true") == 0) || (strcmp(markValue, "1") == 0)) {
+ this->xmpObj.SetProperty ( kXMP_NS_XMP, "Rating", "1", kXMP_DeleteExisting );
+ this->containsXMP = true;
+ }
+ }
+ }
+
+ legacyContext = this->clipMetadata->GetNamedElement ( p2NS, "Location" );
+
+ if ( legacyContext != 0 ) {
+ this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "shotLocation", "PlaceName", false );
+ }
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+} // P2_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// P2_MetaHandler::UpdateFile
+// ==========================
+//
+// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.
+
+void P2_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+
+ LFA_FileRef oldFile = 0;
+ std::string filePath, tempPath;
+
+ // Update the internal legacy XML tree if we have one, and set the digest in the XMP.
+ // *** This is just a minimal prototype.
+
+ bool updateLegacyXML = false;
+
+ if ( this->clipMetadata != 0 ) {
+
+ XMP_Assert ( this->expat != 0 );
+
+ bool xmpFound;
+ std::string xmpValue;
+ XML_Node * xmlNode;
+
+ xmpFound = this->xmpObj.GetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", 0, &xmpValue, 0 );
+
+ if ( xmpFound ) {
+
+ xmlNode = this->ForceChildElement ( this->clipContent, "ClipName", 3 );
+
+ if ( xmpValue != xmlNode->GetLeafContentValue() ) {
+ xmlNode->SetLeafContentValue ( xmpValue.c_str() );
+ updateLegacyXML = true;
+ }
+
+ }
+
+ xmpFound = this->xmpObj.GetArrayItem ( kXMP_NS_DC, "creator", 1, &xmpValue, 0 );
+
+ if ( xmpFound ) {
+ xmlNode = this->ForceChildElement ( this->clipMetadata, "Access", 3 );
+ xmlNode = this->ForceChildElement ( xmlNode, "Creator", 4 );
+ if ( xmpValue != xmlNode->GetLeafContentValue() ) {
+ xmlNode->SetLeafContentValue ( xmpValue.c_str() );
+ updateLegacyXML = true;
+ }
+ }
+
+ }
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "P2", newDigest.c_str(), kXMP_DeleteExisting );
+
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );
+
+ // Update the legacy XML file if necessary.
+
+ if ( updateLegacyXML ) {
+
+ std::string legacyXML;
+ this->expat->tree.Serialize ( &legacyXML );
+
+ this->MakeClipFilePath ( &filePath, ".XML" );
+ oldFile = LFA_Open ( filePath.c_str(), 'w' );
+
+ if ( oldFile == 0 ) {
+
+ // The XML does not exist yet.
+
+ this->MakeClipFilePath ( &filePath, ".XML" );
+ oldFile = LFA_Create ( filePath.c_str() );
+ if ( oldFile == 0 ) XMP_Throw ( "Failure creating P2 legacy XML file", kXMPErr_ExternalFailure );
+ LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() );
+ LFA_Close ( oldFile );
+
+ } else if ( ! doSafeUpdate ) {
+
+ // Over write the existing XML file.
+
+ LFA_Seek ( oldFile, 0, SEEK_SET );
+ LFA_Truncate ( oldFile, 0 );
+ LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() );
+ LFA_Close ( oldFile );
+
+ } else {
+
+ // Do a safe update.
+
+ // *** We really need an LFA_SwapFiles utility.
+
+ this->MakeClipFilePath ( &filePath, ".XML" );
+
+ CreateTempFile ( filePath, &tempPath );
+ LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' );
+ LFA_Write ( tempFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() );
+ LFA_Close ( tempFile );
+
+ LFA_Close ( oldFile );
+ LFA_Delete ( filePath.c_str() );
+ LFA_Rename ( tempPath.c_str(), filePath.c_str() );
+
+ }
+
+ }
+
+ // Update the XMP file.
+
+ oldFile = this->parent->fileRef;
+
+ if ( oldFile == 0 ) {
+
+ // The XMP does not exist yet.
+
+ this->MakeClipFilePath ( &filePath, ".XMP" );
+ oldFile = LFA_Create ( filePath.c_str() );
+ if ( oldFile == 0 ) XMP_Throw ( "Failure creating P2 XMP file", kXMPErr_ExternalFailure );
+ LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( oldFile );
+
+ } else if ( ! doSafeUpdate ) {
+
+ // Over write the existing XMP file.
+
+ LFA_Seek ( oldFile, 0, SEEK_SET );
+ LFA_Truncate ( oldFile, 0 );
+ LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( oldFile );
+
+ } else {
+
+ // Do a safe update.
+
+ // *** We really need an LFA_SwapFiles utility.
+
+ this->MakeClipFilePath ( &filePath, ".XMP" );
+
+ CreateTempFile ( filePath, &tempPath );
+ LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' );
+ LFA_Write ( tempFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( tempFile );
+
+ LFA_Close ( oldFile );
+ LFA_Delete ( filePath.c_str() );
+ LFA_Rename ( tempPath.c_str(), filePath.c_str() );
+
+ }
+
+ this->parent->fileRef = 0;
+
+} // P2_MetaHandler::UpdateFile
+
+// =================================================================================================
+// P2_MetaHandler::WriteFile
+// =========================
+
+void P2_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+
+ // ! WriteFile is not supposed to be called for handlers that own the file.
+ XMP_Throw ( "P2_MetaHandler::WriteFile should not be called", kXMPErr_InternalFailure );
+
+} // P2_MetaHandler::WriteFile
+
+// =================================================================================================
diff --git a/source/XMPFiles/FileHandlers/P2_Handler.hpp b/source/XMPFiles/FileHandlers/P2_Handler.hpp
new file mode 100644
index 0000000..8581fbd
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/P2_Handler.hpp
@@ -0,0 +1,106 @@
+#ifndef __P2_Handler_hpp__
+#define __P2_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles_Impl.hpp"
+
+#include "ExpatAdapter.hpp"
+
+#include "MD5.h"
+
+// =================================================================================================
+/// \file P2_Handler.hpp
+/// \brief Folder format handler for P2.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// *** Could derive from Basic_Handler - buffer file tail in a temp file.
+
+extern XMPFileHandler * P2_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool P2_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kP2_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_UsesSidecarXMP |
+ kXMPFiles_FolderBasedFormat);
+
+class P2_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files.
+ { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); };
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath );
+
+ P2_MetaHandler ( XMPFiles * _parent );
+ virtual ~P2_MetaHandler();
+
+private:
+
+ P2_MetaHandler() : expat(0), clipMetadata(0), clipContent(0) {}; // Hidden on purpose.
+
+ void MakeClipFilePath ( std::string * path, XMP_StringPtr suffix );
+ void MakeLegacyDigest ( std::string * digestStr );
+ void CleanupLegacyXML();
+
+ void DigestLegacyItem ( MD5_CTX & md5Context, XML_NodePtr legacyContext, XMP_StringPtr legacyPropName );
+ void DigestLegacyRelations ( MD5_CTX & md5Context );
+
+ void SetXMPPropertyFromLegacyXML ( bool digestFound,
+ XML_NodePtr legacyContext,
+ XMP_StringPtr schemaNS,
+ XMP_StringPtr propName,
+ XMP_StringPtr legacyPropName,
+ bool isLocalized );
+
+ void SetRelationsFromLegacyXML ( bool digestFound );
+ void SetAudioInfoFromLegacyXML ( bool digestFound );
+ void SetVideoInfoFromLegacyXML ( bool digestFound );
+ void SetDurationFromLegacyXML ( bool digestFound );
+
+ void SetVideoFrameInfoFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound );
+ void SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound );
+
+ XML_Node * ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, int indent = 0 );
+
+ std::string rootPath, clipName, p2NS, defaultNS;
+
+ ExpatAdapter * expat;
+ XML_Node * clipMetadata; // ! Don't delete, points into the Expat tree.
+ XML_Node * clipContent; // ! Don't delete, points into the Expat tree.
+
+}; // P2_MetaHandler
+
+// =================================================================================================
+
+#endif /* __P2_Handler_hpp__ */
diff --git a/source/XMPFiles/FileHandlers/PNG_Handler.cpp b/source/XMPFiles/FileHandlers/PNG_Handler.cpp
index 44a08dd..7e82eaa 100644
--- a/source/XMPFiles/FileHandlers/PNG_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/PNG_Handler.cpp
@@ -138,7 +138,7 @@ void PNG_MetaHandler::ProcessXMP()
XMP_Assert ( this->containsXMP );
XMP_StringPtr packetStr = this->xmpPacket.c_str();
- XMP_StringLen packetLen = this->xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
@@ -160,7 +160,7 @@ void PNG_MetaHandler::UpdateFile ( bool doSafeUpdate )
if ( doSafeUpdate ) XMP_Throw ( "PNG_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
XMP_StringPtr packetStr = xmpPacket.c_str();
- XMP_StringLen packetLen = xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
if ( packetLen == 0 ) return;
LFA_FileRef fileRef(this->parent->fileRef);
@@ -227,7 +227,7 @@ void PNG_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sou
if (PNG_Support::CheckIHDRChunkHeader(chunk))
{
XMP_StringPtr packetStr = xmpPacket.c_str();
- XMP_StringLen packetLen = xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
PNG_Support::WriteXMPChunk(destRef, packetLen, packetStr );
}
diff --git a/source/XMPFiles/FileHandlers/PSD_Handler.cpp b/source/XMPFiles/FileHandlers/PSD_Handler.cpp
index 6d9523e..600be29 100644
--- a/source/XMPFiles/FileHandlers/PSD_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/PSD_Handler.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -249,7 +249,12 @@ void PSD_MetaHandler::ProcessXMP()
this->iptcMgr = new IPTC_Reader();
this->exifMgr = new TIFF_MemoryReader();
} else {
- this->iptcMgr = new IPTC_Writer();
+ #if ! XMP_UNIXBuild
+ this->iptcMgr = new IPTC_Writer(); // ! Parse it later.
+ #else
+ // ! Hack until the legacy-as-local issues are resolved for generic UNIX.
+ this->iptcMgr = new IPTC_Reader(); // ! Import IPTC but don't export it.
+ #endif
this->exifMgr = new TIFF_FileWriter();
}
@@ -287,7 +292,7 @@ void PSD_MetaHandler::ProcessXMP()
XMP_Assert ( this->containsXMP );
// Common code takes care of packetInfo.charForm, .padSize, and .writeable.
XMP_StringPtr packetStr = this->xmpPacket.c_str();
- XMP_StringLen packetLen = this->xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
try {
this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
} catch ( ... ) {
@@ -327,14 +332,20 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate )
if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks.
if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0;
- bool doInPlace = (oldPacketOffset != 0) && (oldPacketLength != 0); // ! Has old packet and new packet fits.
- if ( doInPlace && (this->psirMgr.IsLegacyChanged()) ) doInPlace = false;
+ bool doInPlace = (this->xmpPacket.size() <= (size_t)this->packetInfo.length);
+ if ( this->psirMgr.IsLegacyChanged() ) doInPlace = false;
if ( doInPlace ) {
#if GatherPerformanceData
sAPIPerf->back().extraInfo += ", PSD in-place update";
#endif
+
+ if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) {
+ // They ought to match, cheap to be sure.
+ size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size();
+ this->xmpPacket.append ( extraSpace, ' ' );
+ }
LFA_FileRef liveFile = this->parent->fileRef;
@@ -343,7 +354,7 @@ void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate )
// printf ( "PSD_MetaHandler::UpdateFile - XMP in-place packet offset %lld (0x%llX), size %d\n",
// oldPacketOffset, oldPacketOffset, this->xmpPacket.size() );
LFA_Seek ( liveFile, oldPacketOffset, SEEK_SET );
- LFA_Write ( liveFile, this->xmpPacket.c_str(), this->xmpPacket.size() );
+ LFA_Write ( liveFile, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
} else {
@@ -418,10 +429,10 @@ void PSD_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sou
this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
this->packetInfo.offset = kXMPFiles_UnknownOffset;
- this->packetInfo.length = this->xmpPacket.size();
- this->packetInfo.padSize = GetPacketPadSize ( this->xmpPacket.c_str(), this->xmpPacket.size() );
+ this->packetInfo.length = (XMP_StringLen)this->xmpPacket.size();
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
- this->psirMgr.SetImgRsrc ( kPSIR_XMP, this->xmpPacket.c_str(), this->xmpPacket.size() );
+ this->psirMgr.SetImgRsrc ( kPSIR_XMP, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
// Copy the file header and color mode section, then write the updated image resource section,
// and copy the tail of the source file (layer and mask section to EOF).
diff --git a/source/XMPFiles/FileHandlers/PostScript_Handler.cpp b/source/XMPFiles/FileHandlers/PostScript_Handler.cpp
index 316b37c..2b74646 100644
--- a/source/XMPFiles/FileHandlers/PostScript_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/PostScript_Handler.cpp
@@ -23,7 +23,7 @@ using namespace std;
// =================================================================================================
static const char * kPSFileTag = "%!PS-Adobe-";
-static const int kPSFileTagLen = strlen ( kPSFileTag );
+static const size_t kPSFileTagLen = strlen ( kPSFileTag );
// =================================================================================================
// PostScript_MetaHandlerCTor
@@ -193,10 +193,10 @@ PostScript_MetaHandler::~PostScript_MetaHandler()
// the XMP marker is found, look for the MainFirst/MainLast/NoMain options.
static const char * kPSContainsXMPString = "%ADO_ContainsXMP:";
-static const int kPSContainsXMPLength = strlen ( kPSContainsXMPString );
+static const size_t kPSContainsXMPLength = strlen ( kPSContainsXMPString );
static const char * kPSEndCommentString = "%%EndComments"; // ! Assumed shorter than kPSContainsXMPString.
-static const int kPSEndCommentLength = strlen ( kPSEndCommentString );
+static const size_t kPSEndCommentLength = strlen ( kPSEndCommentString );
int PostScript_MetaHandler::FindPostScriptHint()
{
@@ -430,7 +430,7 @@ bool PostScript_MetaHandler::FindLastPacket()
LFA_Seek ( fileRef, 0, SEEK_SET ); // Seek back to the beginning of the file.
- for ( bufPos = 0; bufPos < fileLen; bufPos += bufLen ) {
+ for ( bufPos = 0; bufPos < (size_t)fileLen; bufPos += bufLen ) {
if ( checkAbort && abortProc(abortArg) ) {
XMP_Throw ( "PostScript_MetaHandler::FindLastPacket - User abort", kXMPErr_UserAbort );
}
diff --git a/source/XMPFiles/FileHandlers/SWF_Handler.cpp b/source/XMPFiles/FileHandlers/SWF_Handler.cpp
new file mode 100644
index 0000000..100f79e
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/SWF_Handler.cpp
@@ -0,0 +1,397 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2007 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "SWF_Handler.hpp"
+
+#include "SWF_Support.hpp"
+
+
+using namespace std;
+
+// =================================================================================================
+/// \file SWF_Handler.hpp
+/// \brief File format handler for SWF.
+///
+/// This handler ...
+///
+// =================================================================================================
+
+// =================================================================================================
+// SWF_MetaHandlerCTor
+// ====================
+
+XMPFileHandler * SWF_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new SWF_MetaHandler ( parent );
+
+} // SWF_MetaHandlerCTor
+
+// =================================================================================================
+// SWF_CheckFormat
+// ===============
+
+bool SWF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles * parent )
+{
+ IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent);
+ XMP_Assert ( format == kXMP_SWFFile );
+
+ IOBuffer ioBuf;
+
+ LFA_Seek ( fileRef, 0, SEEK_SET );
+ if ( ! CheckFileSpace ( fileRef, &ioBuf, SWF_SIGNATURE_LEN ) ) return false;
+
+ if ( !(CheckBytes ( ioBuf.ptr, SWF_F_SIGNATURE_DATA, SWF_SIGNATURE_LEN ) ||
+ CheckBytes(ioBuf.ptr, SWF_C_SIGNATURE_DATA, SWF_SIGNATURE_LEN)) )
+ return false;
+
+ return true;
+
+} // SWF_CheckFormat
+
+// =================================================================================================
+// SWF_MetaHandler::SWF_MetaHandler
+// ==================================
+
+SWF_MetaHandler::SWF_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kSWF_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+}
+
+// =================================================================================================
+// SWF_MetaHandler::~SWF_MetaHandler
+// ===================================
+
+SWF_MetaHandler::~SWF_MetaHandler()
+{
+}
+
+// =================================================================================================
+// SWF_MetaHandler::CacheFileData
+// ===============================
+
+void SWF_MetaHandler::CacheFileData()
+{
+
+ this->containsXMP = false;
+
+ LFA_FileRef fileRef ( this->parent->fileRef );
+ if ( fileRef == 0) return;
+
+ SWF_Support::FileInfo fileInfo(fileRef, this->parent->filePath);
+ IO::InputStream * is = NULL;
+ if(fileInfo.IsCompressed())
+ {
+ XMP_Uns32 fileSize = fileInfo.GetSize();
+ is = new IO::ZIP::DeflateInputStream(fileRef, fileSize);
+
+ IO::ZIP::DeflateInputStream * in = dynamic_cast<IO::ZIP::DeflateInputStream*>(is);
+ in->Skip(SWF_COMPRESSION_BEGIN, IO::ZIP::DEFLATE_NO);
+ }
+ else
+ {
+ is = new IO::FileInputStream(fileRef);
+ is->Skip(SWF_COMPRESSION_BEGIN);
+ }
+
+ SWF_Support::TagState tagState;
+ //important flag to stop iteration over all tags when xmp flag within FileAttributeTag isn't set
+ tagState.cachingFile = true;
+
+ long numTags = SWF_Support::OpenSWF ( is, tagState );
+
+ is->Close();
+ delete is;
+
+ if ( numTags == 0 ) return;
+
+ if (tagState.hasXMP && tagState.xmpLen != 0)
+ {
+ this->xmpPacket.assign(tagState.xmpPacket);
+ this->containsXMP = true;
+ }
+ else
+ {
+ // no XMP
+ }
+
+
+} // SWF_MetaHandler::CacheFileData
+
+// =================================================================================================
+// SWF_MetaHandler::ProcessTNail
+// ==============================
+
+void SWF_MetaHandler::ProcessTNail()
+{
+
+ XMP_Throw ( "SWF_MetaHandler::ProcessTNail isn't implemented yet", kXMPErr_Unimplemented );
+
+} // SWF_MetaHandler::ProcessTNail
+
+// =================================================================================================
+// SWF_MetaHandler::ProcessXMP
+// ============================
+//
+// Process the raw XMP and legacy metadata that was previously cached.
+
+void SWF_MetaHandler::ProcessXMP()
+{
+
+ this->processedXMP = true; // Make sure we only come through here once.
+
+ // Process the XMP packet.
+
+ if ( ! this->xmpPacket.empty() ) {
+
+ XMP_Assert ( this->containsXMP );
+ XMP_StringPtr packetStr = this->xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
+
+ this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
+
+ this->containsXMP = true;
+
+ }
+
+} // SWF_MetaHandler::ProcessXMP
+
+
+// =================================================================================================
+// XMPFileHandler::GetSerializeOptions
+// ===================================
+//
+// Override default implementation to ensure omitting xmp wrapper
+//
+XMP_OptionBits SWF_MetaHandler::GetSerializeOptions()
+{
+ return (kXMP_OmitPacketWrapper | kXMP_OmitAllFormatting | kXMP_OmitXMPMetaElement);
+} // XMPFileHandler::GetSerializeOptions
+
+
+// =================================================================================================
+// SWF_MetaHandler::UpdateFile
+// ============================
+
+void SWF_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ bool updated = false;
+
+ if ( ! this->needsUpdate ) return;
+ if ( doSafeUpdate ) XMP_Throw ( "SWF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
+
+ LFA_FileRef sourceRef = this->parent->fileRef;
+ std::string sourcePath = this->parent->filePath;
+
+ SWF_Support::FileInfo fileInfo(sourceRef, sourcePath);
+
+ if(fileInfo.IsCompressed())
+ sourceRef = fileInfo.Decompress();
+
+ IO::InputStream * is = new IO::FileInputStream(sourceRef);
+ //processing SWF starts after byte SWF_COMPRESSION_BEGIN - currently 8 bytes
+ is->Skip(SWF_COMPRESSION_BEGIN);
+
+ SWF_Support::TagState tagState;
+
+ long numTags = SWF_Support::OpenSWF ( is, tagState );
+
+ //clean objects
+ is->Close();
+ delete is;
+
+ bool foundTag = false;
+ SWF_Support::TailBufferDef tailBuffer;
+
+
+ //find end position to measure tail buffer
+ tailBuffer.tailEndPosition = LFA_Seek(sourceRef, 0, SEEK_END);
+
+ SWF_Support::TagIterator curPos = tagState.tags.begin();
+ SWF_Support::TagIterator endPos = tagState.tags.end();
+
+ for( ;(curPos != endPos) && ! foundTag; ++curPos)
+ {
+ SWF_Support::TagData tag = *curPos;
+
+ //write XMP Tag at the beginning of the file
+ if(! tagState.hasXMP && ! tagState.hasFileAttrTag)
+ {
+ tailBuffer.tailStartPosition = tag.pos;
+ tailBuffer.writePosition = tag.pos;
+ foundTag = true;
+ }
+ //update existing XMP Tag
+ if(tagState.hasXMP && (tagState.xmpTag.pos == tag.pos))
+ {
+ ++curPos;
+ SWF_Support::TagData nextTag = *curPos;
+ tailBuffer.tailStartPosition = nextTag.pos;
+ tailBuffer.writePosition = tagState.xmpTag.pos;
+ foundTag = true;
+ }
+ //write XMP Tag after FileAttribute Tag
+ else if(! tagState.hasXMP && (tag.id == SWF_TAG_ID_FILEATTRIBUTES))
+ {
+ ++curPos;
+ SWF_Support::TagData nextTag = *curPos;
+ tailBuffer.tailStartPosition = nextTag.pos;
+ tailBuffer.writePosition = nextTag.pos;
+ foundTag = true;
+ }
+ }
+
+ //cache tail of file
+ XMP_Assert(tailBuffer.tailEndPosition > tailBuffer.tailStartPosition);
+ XMP_Uns32 tailSize = tailBuffer.GetTailSize();
+ XMP_Uns8 * buffer = new XMP_Uns8[tailSize];
+ SWF_Support::ReadBuffer(sourceRef, tailBuffer.tailStartPosition, tailSize, buffer);
+
+ //write new XMP packet
+ XMP_StringPtr packetStr = xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
+
+ LFA_Seek(sourceRef, tailBuffer.writePosition, SEEK_SET);
+ SWF_Support::WriteXMPTag(sourceRef, packetLen, packetStr);
+
+ // truncate to minimal size
+ LFA_Truncate(sourceRef, LFA_Tell(sourceRef));
+
+ //move tail of the file
+ LFA_Write(sourceRef, buffer, tailSize);
+
+ //cleaning buffer
+ delete [] buffer;
+
+ //update FileAttribute Tag if exists
+ if(tagState.hasFileAttrTag)
+ SWF_Support::UpdateFileAttrTag(sourceRef, tagState.fileAttrTag, tagState);
+
+
+ //update File Size
+ SWF_Support::UpdateHeader(sourceRef);
+
+ //compress file after writing XMP
+ if(fileInfo.IsCompressed())
+ {
+ fileInfo.Compress(sourceRef, this->parent->fileRef);
+ fileInfo.Clean();
+ }
+
+
+ if ( ! updated )return; // If there's an error writing the chunk, bail.
+
+ this->needsUpdate = false;
+
+} // SWF_MetaHandler::UpdateFile
+
+// =================================================================================================
+// SWF_MetaHandler::WriteFile
+// ===========================
+
+void SWF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+
+ LFA_FileRef destRef = this->parent->fileRef;
+
+ SWF_Support::TagState tagState;
+
+ LFA_FileRef origRef(destRef);
+ std::string updatePath;
+
+
+ SWF_Support::FileInfo fileInfo(sourceRef, sourcePath);
+
+ if(fileInfo.IsCompressed())
+ {
+ sourceRef = fileInfo.Decompress();
+ CreateTempFile ( sourcePath, &updatePath, kCopyMacRsrc );
+ destRef = LFA_Open ( updatePath.c_str(), 'w' );
+ }
+
+ IO::InputStream * is = NULL;
+
+ is = new IO::FileInputStream(sourceRef);
+ is->Skip(SWF_COMPRESSION_BEGIN);
+
+ long numTags = SWF_Support::OpenSWF( is, tagState );
+
+ is->Close();
+ delete is;
+
+ if ( numTags == 0 ) return;
+
+ LFA_Truncate(destRef, 0);
+ SWF_Support::CopyHeader(sourceRef, destRef, tagState);
+
+ SWF_Support::TagIterator curPos = tagState.tags.begin();
+ SWF_Support::TagIterator endPos = tagState.tags.end();
+
+ XMP_StringPtr packetStr = xmpPacket.c_str();
+ XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size();
+
+ bool copying = true;
+ bool isXMPTagWritten = false;
+
+ for (; (curPos != endPos); ++curPos)
+ {
+ SWF_Support::TagData tag = *curPos;
+
+ // write XMP tag right after FileAttribute Tag if no XMP tag exists
+ if (! tagState.hasXMP && (tag.id == SWF_TAG_ID_FILEATTRIBUTES))
+ SWF_Support::WriteXMPTag(destRef, packetLen, packetStr );
+
+ //no FileAttribute Tag and no XMP tag write XMP Tag at the beginning of the file
+ if(!tagState.hasXMP && !tagState.hasFileAttrTag && ! isXMPTagWritten)
+ {
+ isXMPTagWritten = true;
+ SWF_Support::WriteXMPTag(destRef, packetLen, packetStr );
+ }
+
+ // write XMP tag where old XMP exists
+ if(tagState.hasXMP && (tag.pos == tagState.xmpTag.pos))
+ {
+ copying = false;
+ SWF_Support::WriteXMPTag(destRef, packetLen, packetStr );
+ }
+
+ // copy any other chunk
+ if(copying)
+ SWF_Support::CopyTag(sourceRef, destRef, tag);
+
+ copying = true;
+ }
+
+ // update FileAttribute Tag in new file
+ if(tagState.hasFileAttrTag)
+ SWF_Support::UpdateFileAttrTag(destRef, tagState.fileAttrTag, tagState);
+
+
+ // update file header by measuring new file size
+ SWF_Support::UpdateHeader(origRef);
+
+ //compress re-written file
+ if(fileInfo.IsCompressed())
+ {
+ fileInfo.Compress(destRef, origRef);
+ fileInfo.Clean();
+ LFA_Close(destRef);
+ LFA_Delete(updatePath.c_str());
+ }
+
+
+
+
+} // SWF_MetaHandler::WriteFile
+
diff --git a/source/XMPFiles/FileHandlers/SWF_Handler.hpp b/source/XMPFiles/FileHandlers/SWF_Handler.hpp
new file mode 100644
index 0000000..511ddd6
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/SWF_Handler.hpp
@@ -0,0 +1,65 @@
+#ifndef __SWF_Handler_hpp__
+#define __SWF_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2007 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+//#include "XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles_Impl.hpp"
+
+//#include "SWF_Support.hpp"
+
+// =================================================================================================
+/// \file SWF_Handler.hpp
+/// \brief File format handler for SWF.
+///
+/// This header ...
+///
+// =================================================================================================
+
+// *** Could derive from Basic_Handler - buffer file tail in a temp file.
+
+extern XMPFileHandler* SWF_MetaHandlerCTor ( XMPFiles* parent );
+
+extern bool SWF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles* parent );
+
+static const XMP_OptionBits kSWF_HandlerFlags = ( kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket
+ );
+
+class SWF_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessTNail();
+ void ProcessXMP();
+
+ XMP_OptionBits GetSerializeOptions();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteFile ( LFA_FileRef sourceRef, const std::string& sourcePath );
+
+ SWF_MetaHandler ( XMPFiles* parent );
+ virtual ~SWF_MetaHandler();
+
+
+
+}; // SWF_MetaHandler
+
+// =================================================================================================
+
+#endif /* __SWF_Handler_hpp__ */
diff --git a/source/XMPFiles/FileHandlers/Scanner_Handler.cpp b/source/XMPFiles/FileHandlers/Scanner_Handler.cpp
index 8d704a6..4959ca0 100644
--- a/source/XMPFiles/FileHandlers/Scanner_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/Scanner_Handler.cpp
@@ -76,7 +76,7 @@ PickMainPacket ( std::vector<CandidateInfo>& candidates, bool beLenient )
int main = -1; // Assume the worst.
XMP_OptionBits options;
- int metaCount = candidates.size();
+ int metaCount = (int)candidates.size();
if ( metaCount == 0 ) return -1;
if ( metaCount == 1 ) return 0;
@@ -281,10 +281,10 @@ void Scanner_MetaHandler::CacheFileData()
try {
for ( bufPos = 0; bufPos < snips[pkt].fLength; bufPos += bufLen ) {
bufLen = kBufferSize;
- if ( (bufPos + bufLen) > snips[pkt].fLength ) bufLen = size_t ( snips[pkt].fLength - bufPos );
- (void) LFA_Read ( fileRef, buffer, bufLen, kLFA_RequireAll );
+ if ( (bufPos + bufLen) > (size_t)snips[pkt].fLength ) bufLen = size_t ( snips[pkt].fLength - bufPos );
+ (void) LFA_Read ( fileRef, buffer, (XMP_Int32)bufLen, kLFA_RequireAll );
xmpPacket.append ( (const char *)buffer, bufLen );
- newMeta->ParseFromBuffer ( (char *)buffer, bufLen, kXMP_ParseMoreBuffers );
+ newMeta->ParseFromBuffer ( (char *)buffer, (XMP_StringLen)bufLen, kXMP_ParseMoreBuffers );
}
newMeta->ParseFromBuffer ( 0, 0, kXMP_NoOptions );
} catch ( ... ) {
diff --git a/source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp b/source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp
new file mode 100644
index 0000000..2dca918
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp
@@ -0,0 +1,782 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "SonyHDV_Handler.hpp"
+
+#include "MD5.h"
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+using namespace std;
+
+// =================================================================================================
+/// \file SonyHDV_Handler.cpp
+/// \brief Folder format handler for Sony HDV.
+///
+/// This handler is for the Sony HDV video format. This is a pseudo-package, visible files but with
+/// a very well-defined layout and naming rules.
+///
+/// A typical Sony HDV layout looks like:
+///
+/// .../MyMovie/
+/// VIDEO/
+/// HDV/
+/// 00_0001_2007-08-06_165555.IDX
+/// 00_0001_2007-08-06_165555.M2T
+/// 00_0001_2007-08-06_171740.M2T
+/// 00_0001_2007-08-06_171740.M2T.ese
+/// tracks.dat
+///
+/// The logical clip name can be "00_0001" or "00_0001_" plus anything. We'll find the .IDX file,
+/// which defines the existence of the clip. Full file names as input will pull out the camera/clip
+/// parts and match in the same way. The .XMP file will use the date/time suffix from the .IDX file.
+// =================================================================================================
+
+// =================================================================================================
+// SonyHDV_CheckFormat
+// ===================
+//
+// This version does fairly simple checks. The top level folder (.../MyMovie) must contain the
+// VIDEO/HVR subtree. The HVR folder must contain a .IDX file for the desired clip. The name checks
+// are case insensitive.
+//
+// The state of the string parameters depends on the form of the path passed by the client. If the
+// client passed a logical clip path, like ".../MyMovie/00_0001", the parameters are:
+// rootPath - ".../MyMovie"
+// gpName - empty
+// parentName - empty
+// leafName - "00_0001"
+//
+// If the client passed a full file path, like ".../MyMovie/VIDEO/HVR/00_0001_2007-08-06_165555.M2T",
+// they are:
+// rootPath - ".../MyMovie"
+// gpName - "VIDEO"
+// parentName - "HVR"
+// leafName - "00_0001_2007-08-06_165555.M2T"
+//
+// The logical clip name can be short like "00_0001", or long like "00_0001_2007-08-06_165555". We
+// only key off of the portion before a second underscore.
+
+// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has
+// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
+// ! file exists for a full file path.
+
+bool SonyHDV_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ // Do some basic checks on the root path and component names.
+
+ if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty.
+
+ std::string tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "VIDEO";
+
+ if ( gpName.empty() ) {
+ // This is the logical clip path case. Look for VIDEO/HVR subtree.
+ if ( GetChildMode ( tempPath, "HVR" ) != kFMode_IsFolder ) return false;
+ } else {
+ // This is the existing file case. Check the parent and grandparent names.
+ if ( (gpName != "VIDEO") || (parentName != "HVR") ) return false;
+ }
+
+ // Look for the clip's .IDX file. If found use that as the full clip name.
+
+ tempPath += kDirChar;
+ tempPath += "HVR";
+
+ std::string clipName = leafName;
+ int usCount = 0;
+ size_t i, limit = leafName.size();
+ for ( i = 0; i < limit; ++i ) {
+ if ( clipName[i] == '_' ) {
+ ++usCount;
+ if ( usCount == 2 ) break;
+ }
+ }
+ if ( i < limit ) clipName.erase ( i );
+ clipName += '_'; // Make sure a final '_' is there for the search comparisons.
+
+ XMP_FolderInfo folderInfo;
+ std::string childName;
+ bool found = false;
+
+ folderInfo.Open ( tempPath.c_str() );
+ while ( (! found) && folderInfo.GetNextChild ( &childName ) ) {
+ size_t childLen = childName.size();
+ if ( childLen < 4 ) continue;
+ MakeUpperCase ( &childName );
+ if ( childName.compare ( childLen-4, 4, ".IDX" ) != 0 ) continue;
+ if ( childName.compare ( 0, clipName.size(), clipName ) == 0 ) {
+ found = true;
+ clipName = childName;
+ clipName.erase ( childLen-4 );
+ }
+ }
+ if ( ! found ) return false;
+
+
+ // Disabled until Sony HDV clip spanning is supported.
+ // Since segments of spanned clips are currently considered separate entities,
+ // information such as frame count needs to be considered on a per segment basis. JPM
+ clipName = leafName;
+
+ // Set tempPath to <root>/<clip-name>, e.g. ".../MyMovie/00_0001_2007-08-06_165555". This is
+ // passed to the handler via the handlerTemp hackery.
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += clipName;
+
+ size_t pathLen = tempPath.size() + 1; // Include a terminating nul.
+ parent->handlerTemp = malloc ( pathLen );
+ if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for SonyHDV clip info", kXMPErr_NoMemory );
+ memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above.
+
+ return true;
+
+} // SonyHDV_CheckFormat
+
+// =================================================================================================
+// ReadIDXFile
+// ===========
+
+#define ExtractTimeCodeByte(ch,mask) ( (((ch & mask) >> 4) * 10) + (ch & 0xF) )
+
+static bool ReadIDXFile ( const std::string& idxPath,
+ const std::string& clipName,
+ SXMPMeta* xmpObj,
+ bool& containsXMP,
+ MD5_CTX* md5Context,
+ bool digestFound )
+{
+ bool result = true;
+ containsXMP = false;
+
+ if ( clipName.size() != 25 ) return false;
+
+ try {
+
+
+ AutoFile idxFile;
+ idxFile.fileRef = LFA_Open ( idxPath.c_str(), 'r' );
+ if ( idxFile.fileRef == 0 ) return false; // The open failed.
+
+ struct SHDV_HeaderBlock
+ {
+ char mHeader[8];
+ unsigned char mValidFlag;
+ unsigned char mReserved;
+ unsigned char mECCTB;
+ unsigned char mSignalMode;
+ unsigned char mFileThousands;
+ unsigned char mFileHundreds;
+ unsigned char mFileTens;
+ unsigned char mFileUnits;
+ };
+
+ SHDV_HeaderBlock hdvHeaderBlock;
+ memset ( &hdvHeaderBlock, 0, sizeof(SHDV_HeaderBlock) );
+
+ LFA_Read ( idxFile.fileRef, hdvHeaderBlock.mHeader, 8 );
+ LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mValidFlag, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mReserved, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mECCTB, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mSignalMode, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mFileThousands, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mFileHundreds, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mFileTens, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvHeaderBlock.mFileUnits, 1 );
+
+ const int fileCount = (hdvHeaderBlock.mFileThousands - '0') * 1000 +
+ (hdvHeaderBlock.mFileHundreds - '0') * 100 +
+ (hdvHeaderBlock.mFileTens - '0') * 10 +
+ (hdvHeaderBlock.mFileUnits - '0');
+
+ // Read file info block.
+ struct SHDV_FileBlock
+ {
+ char mDT[2];
+ unsigned char mFileNameYear;
+ unsigned char mFileNameMonth;
+ unsigned char mFileNameDay;
+ unsigned char mFileNameHour;
+ unsigned char mFileNameMinute;
+ unsigned char mFileNameSecond;
+ unsigned char mStartTimeCode[4];
+ unsigned char mTotalFrame[4];
+ };
+
+ SHDV_FileBlock hdvFileBlock;
+ memset ( &hdvFileBlock, 0, sizeof(SHDV_FileBlock) );
+
+ char filenameBuffer[256];
+ std::string fileDateAndTime = clipName.substr(8);
+
+ bool foundFileBlock = false;
+
+ for ( int i=0; ((i < fileCount) && (! foundFileBlock)); ++i ) {
+
+ LFA_Read ( idxFile.fileRef, hdvFileBlock.mDT, 2 );
+ LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameYear, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameMonth, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameDay, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameHour, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameMinute, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvFileBlock.mFileNameSecond, 1 );
+ LFA_Read ( idxFile.fileRef, &hdvFileBlock.mStartTimeCode, 4 );
+ LFA_Read ( idxFile.fileRef, &hdvFileBlock.mTotalFrame, 4 );
+
+ // Compose file name we expect from file contents and break out on match.
+ sprintf ( filenameBuffer, "%02d-%02d-%02d_%02d%02d%02d",
+ hdvFileBlock.mFileNameYear + 2000,
+ hdvFileBlock.mFileNameMonth,
+ hdvFileBlock.mFileNameDay,
+ hdvFileBlock.mFileNameHour,
+ hdvFileBlock.mFileNameMinute,
+ hdvFileBlock.mFileNameSecond );
+
+ foundFileBlock = (fileDateAndTime==filenameBuffer);
+
+ }
+
+ LFA_Close ( idxFile.fileRef );
+ idxFile.fileRef = 0;
+
+ if ( ! foundFileBlock ) return false;
+
+ // If digest calculation requested, calculate it and return.
+ if ( md5Context != 0 ) {
+ MD5Update ( md5Context, (XMP_Uns8*)(&hdvHeaderBlock), sizeof(SHDV_HeaderBlock) );
+ MD5Update ( md5Context, (XMP_Uns8*)(&hdvFileBlock), sizeof(SHDV_FileBlock) );
+ }
+
+ // The xmpObj parameter must be provided in order to extract XMP
+ if ( xmpObj == 0 ) return (md5Context != 0);
+
+ // Standard def?
+ const bool isSD = ((hdvHeaderBlock.mSignalMode == 0x80) || (hdvHeaderBlock.mSignalMode == 0));
+
+ // Progressive vs interlaced extracted from high bit of ECCTB byte
+ const bool clipIsProgressive = ((hdvHeaderBlock.mECCTB & 0x80) != 0);
+
+ // Lowest three bits contain frame rate information
+ const int sfr = (hdvHeaderBlock.mECCTB & 7) + (clipIsProgressive ? 0 : 8);
+
+ // Sample scale and sample size.
+ int clipSampleScale = 0;
+ int clipSampleSize = 0;
+ std::string frameRate;
+
+ // Frame rate
+ switch ( sfr ) {
+ case 0 : break; // Not valid in spec, but it's happening in test files.
+ case 1 : clipSampleScale = 24000; clipSampleSize = 1001; frameRate = "23.98p"; break;
+ case 3 : clipSampleScale = 25; clipSampleSize = 1; frameRate = "25p"; break;
+ case 4 : clipSampleScale = 30000; clipSampleSize = 1001; frameRate = "29.97p"; break;
+ case 11 : clipSampleScale = 25; clipSampleSize = 1; frameRate = "50i"; break;
+ case 12 : clipSampleScale = 30000; clipSampleSize = 1001; frameRate = "59.94i"; break;
+ }
+
+ containsXMP = true;
+
+ // Frame size and PAR for HD (not clear on SD yet).
+ std::string xmpString;
+ XMP_StringPtr xmpValue = 0;
+
+ if ( ! isSD ) {
+
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoFrameSize" )) ) {
+
+ xmpValue = "1440";
+ xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "w", &xmpString, 0 );
+ if ( xmpString != xmpValue ) {
+ xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", xmpValue, 0 );
+ }
+
+ xmpValue = "1080";
+ xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "h", &xmpString, 0 );
+ if ( xmpString != xmpValue ) {
+ xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", xmpValue, 0 );
+ }
+
+ xmpValue = "pixels";
+ xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "unit", &xmpString, 0 );
+ if ( xmpString != xmpValue ) {
+ xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", xmpValue, 0 );
+ }
+ }
+
+ xmpValue = "4/3";
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoPixelAspectRatio" )) ) {
+ xmpObj->SetProperty ( kXMP_NS_DM, "videoPixelAspectRatio", xmpValue, kXMP_DeleteExisting );
+ }
+
+ }
+
+ // Sample size and scale.
+ if ( clipSampleScale != 0 ) {
+
+ char buffer[255];
+
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimeScale" )) ) {
+ sprintf(buffer, "%d", clipSampleScale);
+ xmpValue = buffer;
+ xmpObj->SetProperty ( kXMP_NS_DM, "startTimeScale", xmpValue, kXMP_DeleteExisting );
+ }
+
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimeSampleSize" )) ) {
+ sprintf(buffer, "%d", clipSampleSize);
+ xmpValue = buffer;
+ xmpObj->SetProperty ( kXMP_NS_DM, "startTimeSampleSize", xmpValue, kXMP_DeleteExisting );
+ }
+
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) {
+
+ const int frameCount = (hdvFileBlock.mTotalFrame[0] << 24) + (hdvFileBlock.mTotalFrame[1] << 16) +
+ (hdvFileBlock.mTotalFrame[2] << 8) + hdvFileBlock.mTotalFrame[3];
+
+ sprintf ( buffer, "%d", frameCount );
+ xmpValue = buffer;
+ xmpObj->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", xmpValue, 0 );
+
+ sprintf ( buffer, "%d/%d", clipSampleSize, clipSampleScale );
+ xmpValue = buffer;
+ xmpObj->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", xmpValue, 0 );
+
+ }
+
+ }
+
+ // Time Code.
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimecode" )) ) {
+
+ if ( (clipSampleScale != 0) && (clipSampleSize != 0) ) {
+
+ const bool dropFrame = ( (0x40 & hdvFileBlock.mStartTimeCode[0]) != 0 ) && ( sfr == 4 || sfr == 12 );
+ const char chDF = dropFrame ? ';' : ':';
+ const int tcFrames = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[0], 0x30 );
+ const int tcSeconds = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[1], 0x70 );
+ const int tcMinutes = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[2], 0x70 );
+ const int tcHours = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[3], 0x30 );
+
+ // HH:MM:SS:FF or HH;MM;SS;FF
+ char timecode[256];
+ sprintf ( timecode, "%02d%c%02d%c%02d%c%02d", tcHours, chDF, tcMinutes, chDF, tcSeconds, chDF, tcFrames );
+ std::string sonyTimeString = timecode;
+
+ xmpObj->GetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", &xmpString, 0 );
+ if ( xmpString != sonyTimeString ) {
+
+ xmpObj->SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", sonyTimeString, 0 );
+
+ std::string timeFormat;
+ if ( clipSampleSize == 1 ) {
+
+ // 24, 25, 40, 50, 60
+ switch ( clipSampleScale ) {
+ case 24 : timeFormat = "24"; break;
+ case 25 : timeFormat = "25"; break;
+ case 50 : timeFormat = "50"; break;
+ default : XMP_Assert ( false );
+ }
+
+ timeFormat += "Timecode";
+
+ } else {
+
+ // 23.976, 29.97, 59.94
+ XMP_Assert ( clipSampleSize == 1001 );
+ switch ( clipSampleScale ) {
+ case 24000 : timeFormat = "23976"; break;
+ case 30000 : timeFormat = "2997"; break;
+ case 60000 : timeFormat = "5994"; break;
+ default : XMP_Assert( false ); break;
+ }
+
+ timeFormat += dropFrame ? "DropTimecode" : "NonDropTimecode";
+
+ }
+
+ xmpObj->SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", timeFormat, 0 );
+
+ }
+
+ }
+
+ }
+
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "CreateDate" )) ) {
+
+ // Clip has date and time in the case of DT (otherwise date and time haven't been set).
+ bool clipHasDate = ((hdvFileBlock.mDT[0] == 'D') && (hdvFileBlock.mDT[1] == 'T'));
+
+ // Creation date
+ if ( clipHasDate ) {
+
+ // YYYY-MM-DDThh:mm:ssZ
+ char date[256];
+ sprintf ( date, "%4d-%02d-%02dT%02d:%02d:%02dZ",
+ hdvFileBlock.mFileNameYear + 2000,
+ hdvFileBlock.mFileNameMonth,
+ hdvFileBlock.mFileNameDay,
+ hdvFileBlock.mFileNameHour,
+ hdvFileBlock.mFileNameMinute,
+ hdvFileBlock.mFileNameSecond );
+
+ XMP_StringPtr xmpDate = date;
+ xmpObj->SetProperty ( kXMP_NS_XMP, "CreateDate", xmpDate, kXMP_DeleteExisting );
+
+ }
+
+ }
+
+ // Frame rate.
+ if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoFrameRate" )) ) {
+
+ if ( frameRate.size() != 0 ) {
+ xmpString = frameRate;
+ xmpObj->SetProperty ( kXMP_NS_DM, "videoFrameRate", xmpString, kXMP_DeleteExisting );
+ }
+
+ }
+
+ } catch ( ... ) {
+
+ result = false;
+
+ }
+
+ return result;
+
+} // ReadIDXFile
+
+// =================================================================================================
+// SonyHDV_MetaHandlerCTor
+// =======================
+
+XMPFileHandler * SonyHDV_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new SonyHDV_MetaHandler ( parent );
+
+} // SonyHDV_MetaHandlerCTor
+
+// =================================================================================================
+// SonyHDV_MetaHandler::SonyHDV_MetaHandler
+// ========================================
+
+SonyHDV_MetaHandler::SonyHDV_MetaHandler ( XMPFiles * _parent )
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kSonyHDV_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ // Extract the root path and clip name.
+
+ XMP_Assert ( this->parent->handlerTemp != 0 );
+
+ this->rootPath.assign ( (char*) this->parent->handlerTemp );
+ free ( this->parent->handlerTemp );
+ this->parent->handlerTemp = 0;
+
+ SplitLeafName ( &this->rootPath, &this->clipName );
+
+} // SonyHDV_MetaHandler::SonyHDV_MetaHandler
+
+// =================================================================================================
+// SonyHDV_MetaHandler::~SonyHDV_MetaHandler
+// =========================================
+
+SonyHDV_MetaHandler::~SonyHDV_MetaHandler()
+{
+
+ if ( this->parent->handlerTemp != 0 ) {
+ free ( this->parent->handlerTemp );
+ this->parent->handlerTemp = 0;
+ }
+
+} // SonyHDV_MetaHandler::~SonyHDV_MetaHandler
+
+// =================================================================================================
+// SonyHDV_MetaHandler::MakeClipFilePath
+// =====================================
+
+void SonyHDV_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix )
+{
+
+ *path = this->rootPath;
+ *path += kDirChar;
+ *path += "VIDEO";
+ *path += kDirChar;
+ *path += "HVR";
+ *path += kDirChar;
+ *path += this->clipName;
+ *path += suffix;
+
+} // SonyHDV_MetaHandler::MakeClipFilePath
+
+// =================================================================================================
+// SonyHDV_MetaHandler::MakeIndexFilePath
+// ======================================
+
+bool SonyHDV_MetaHandler::MakeIndexFilePath ( std::string& idxPath, const std::string& rootPath, const std::string& leafName )
+{
+ std::string tempPath;
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "VIDEO";
+ tempPath += kDirChar;
+ tempPath += "HVR";
+
+ idxPath = tempPath;
+ idxPath += kDirChar;
+ idxPath += leafName;
+ idxPath += ".IDX";
+
+ // Default case
+ if ( GetFileMode ( idxPath.c_str() ) == kFMode_IsFile ) return true;
+
+ // Spanned clip case
+
+ // Scanning code taken from SonyHDV_CheckFormat
+ // Can be isolated to a separate function.
+
+ std::string clipName = leafName;
+ int usCount = 0;
+ size_t i, limit = leafName.size();
+
+ for ( i = 0; i < limit; ++i ) {
+ if ( clipName[i] == '_' ) {
+ ++usCount;
+ if ( usCount == 2 ) break;
+ }
+ }
+
+ if ( i < limit ) clipName.erase ( i );
+ clipName += '_'; // Make sure a final '_' is there for the search comparisons.
+
+ XMP_FolderInfo folderInfo;
+ std::string childName;
+ bool found = false;
+
+ folderInfo.Open ( tempPath.c_str() );
+ while ( (! found) && folderInfo.GetNextChild ( &childName ) ) {
+ size_t childLen = childName.size();
+ if ( childLen < 4 ) continue;
+ MakeUpperCase ( &childName );
+ if ( childName.compare ( childLen-4, 4, ".IDX" ) != 0 ) continue;
+ if ( childName.compare ( 0, clipName.size(), clipName ) == 0 ) {
+ found = true;
+ clipName = childName;
+ clipName.erase ( childLen-4 );
+ }
+ }
+ if ( ! found ) return false;
+
+ idxPath = tempPath;
+ idxPath += kDirChar;
+ idxPath += clipName;
+ idxPath += ".IDX";
+
+ return true;
+}
+
+// =================================================================================================
+// SonyHDV_MetaHandler::MakeLegacyDigest
+// =====================================
+
+#define kHexDigits "0123456789ABCDEF"
+
+void SonyHDV_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ std::string idxPath;
+ if ( ! this->MakeIndexFilePath ( idxPath, this->rootPath, this->clipName ) ) return;
+
+ MD5_CTX context;
+ unsigned char digestBin [16];
+ bool dummy = false;
+ MD5Init ( &context );
+ ReadIDXFile ( idxPath, this->clipName, 0, dummy, &context, false );
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->erase();
+ digestStr->append ( buffer, 32 );
+
+} // MakeLegacyDigest
+
+// =================================================================================================
+// SonyHDV_MetaHandler::CacheFileData
+// ==================================
+
+void SonyHDV_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) );
+
+ // See if the clip's .XMP file exists.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, ".XMP" );
+ if ( GetFileMode ( xmpPath.c_str() ) != kFMode_IsFile ) return; // No XMP.
+
+ // Read the entire .XMP file.
+
+ char openMode = 'r';
+ if ( this->parent->openFlags & kXMPFiles_OpenForUpdate ) openMode = 'w';
+
+ LFA_FileRef xmpFile = LFA_Open ( xmpPath.c_str(), openMode );
+ if ( xmpFile == 0 ) return; // The open failed.
+
+ XMP_Int64 xmpLen = LFA_Measure ( xmpFile );
+ if ( xmpLen > 100*1024*1024 ) {
+ XMP_Throw ( "SonyHDV XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check.
+ }
+
+ this->xmpPacket.erase();
+ this->xmpPacket.reserve ( (size_t)xmpLen );
+ this->xmpPacket.append ( (size_t)xmpLen, ' ' );
+
+ XMP_Int32 ioCount = LFA_Read ( xmpFile, (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen, kLFA_RequireAll );
+ XMP_Assert ( ioCount == xmpLen );
+
+ this->packetInfo.offset = 0;
+ this->packetInfo.length = (XMP_Int32)xmpLen;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ XMP_Assert ( this->parent->fileRef == 0 );
+ if ( openMode == 'r' ) {
+ LFA_Close ( xmpFile );
+ } else {
+ this->parent->fileRef = xmpFile;
+ }
+
+ this->containsXMP = true;
+
+} // SonyHDV_MetaHandler::CacheFileData
+
+// =================================================================================================
+// SonyHDV_MetaHandler::ProcessXMP
+// ===============================
+
+void SonyHDV_MetaHandler::ProcessXMP()
+{
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ // Check the legacy digest.
+ std::string oldDigest, newDigest;
+ bool digestFound;
+ digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "SonyHDV", &oldDigest, 0 );
+ if ( digestFound ) {
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) return;
+ }
+
+ // Read the IDX legacy.
+ std::string idxPath;
+ if ( ! this->MakeIndexFilePath ( idxPath, this->rootPath, this->clipName ) ) return;
+ ReadIDXFile ( idxPath, this->clipName, &this->xmpObj, this->containsXMP, 0, digestFound );
+
+} // SonyHDV_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// SonyHDV_MetaHandler::UpdateFile
+// ===============================
+//
+// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.
+
+void SonyHDV_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "SonyHDV", newDigest.c_str(), kXMP_DeleteExisting );
+
+ LFA_FileRef oldFile = this->parent->fileRef;
+
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );
+
+ if ( oldFile == 0 ) {
+
+ // The XMP does not exist yet.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, ".XMP" );
+
+ LFA_FileRef xmpFile = LFA_Create ( xmpPath.c_str() );
+ if ( xmpFile == 0 ) XMP_Throw ( "Failure creating SonyHDV XMP file", kXMPErr_ExternalFailure );
+ LFA_Write ( xmpFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( xmpFile );
+
+ } else if ( ! doSafeUpdate ) {
+
+ // Over write the existing XMP file.
+
+ LFA_Seek ( oldFile, 0, SEEK_SET );
+ LFA_Truncate ( oldFile, 0 );
+ LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( oldFile );
+
+ } else {
+
+ // Do a safe update.
+
+ // *** We really need an LFA_SwapFiles utility.
+
+ std::string xmpPath, tempPath;
+
+ this->MakeClipFilePath ( &xmpPath, ".XMP" );
+
+ CreateTempFile ( xmpPath, &tempPath );
+ LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' );
+ LFA_Write ( tempFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( tempFile );
+
+ LFA_Close ( oldFile );
+ LFA_Delete ( xmpPath.c_str() );
+ LFA_Rename ( tempPath.c_str(), xmpPath.c_str() );
+
+ }
+
+ this->parent->fileRef = 0;
+
+} // SonyHDV_MetaHandler::UpdateFile
+
+// =================================================================================================
+// SonyHDV_MetaHandler::WriteFile
+// ==============================
+
+void SonyHDV_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+
+ // ! WriteFile is not supposed to be called for handlers that own the file.
+ XMP_Throw ( "SonyHDV_MetaHandler::WriteFile should not be called", kXMPErr_InternalFailure );
+
+} // SonyHDV_MetaHandler::WriteFile
+
+// =================================================================================================
diff --git a/source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp b/source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp
new file mode 100644
index 0000000..a69f0dc
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/SonyHDV_Handler.hpp
@@ -0,0 +1,77 @@
+#ifndef __SonyHDV_Handler_hpp__
+#define __SonyHDV_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles_Impl.hpp"
+
+#include "ExpatAdapter.hpp"
+
+// =================================================================================================
+/// \file SonyHDV_Handler.hpp
+/// \brief Folder format handler for SonyHDV.
+///
+/// This header ...
+///
+// =================================================================================================
+
+extern XMPFileHandler * SonyHDV_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool SonyHDV_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kSonyHDV_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_FolderBasedFormat);
+
+class SonyHDV_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files.
+ { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); };
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath );
+
+ SonyHDV_MetaHandler ( XMPFiles * _parent );
+ virtual ~SonyHDV_MetaHandler();
+
+private:
+
+ SonyHDV_MetaHandler() {}; // Hidden on purpose.
+
+ void MakeClipFilePath ( std::string * path, XMP_StringPtr suffix );
+ bool MakeIndexFilePath ( std::string& idxPath, const std::string& rootPath, const std::string& leafName );
+ void MakeLegacyDigest ( std::string * digestStr );
+
+ std::string rootPath, clipName;
+
+}; // SonyHDV_MetaHandler
+
+// =================================================================================================
+
+#endif /* __SonyHDV_Handler_hpp__ */
diff --git a/source/XMPFiles/FileHandlers/TIFF_Handler.cpp b/source/XMPFiles/FileHandlers/TIFF_Handler.cpp
index e63b28c..a12e718 100644
--- a/source/XMPFiles/FileHandlers/TIFF_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/TIFF_Handler.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -120,6 +120,22 @@ void TIFF_MetaHandler::CacheFileData()
this->tiffMgr.ParseFileStream ( fileRef );
+ TIFF_Manager::TagInfo dngInfo;
+ if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, &dngInfo ) ) {
+
+ // Reject DNG files that are version 2.0 or beyond, this is being written at the time of
+ // DNG version 1.2. The DNG team says it is OK to use 2.0, not strictly 1.2. Use the
+ // DNGBackwardVersion if it is present, else the DNGVersion. Note that the version value is
+ // supposed to be type BYTE, so the file order is always essentially big endian.
+
+ XMP_Uns8 majorVersion = *((XMP_Uns8*)dngInfo.dataPtr); // Start with DNGVersion.
+ if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGBackwardVersion, &dngInfo ) ) {
+ majorVersion = *((XMP_Uns8*)dngInfo.dataPtr); // Use DNGBackwardVersion if possible.
+ }
+ if ( majorVersion > 1 ) XMP_Throw ( "DNG version beyond 1.x", kXMPErr_BadTIFF );
+
+ }
+
TIFF_Manager::TagInfo xmpInfo;
bool found = this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, &xmpInfo );
@@ -198,7 +214,12 @@ void TIFF_MetaHandler::ProcessXMP()
this->iptcMgr = new IPTC_Reader();
} else {
this->psirMgr = new PSIR_FileWriter();
- this->iptcMgr = new IPTC_Writer();
+ #if ! XMP_UNIXBuild
+ this->iptcMgr = new IPTC_Writer(); // ! Parse it later.
+ #else
+ // ! Hack until the legacy-as-local issues are resolved for generic UNIX.
+ this->iptcMgr = new IPTC_Reader(); // ! Import IPTC but don't export it.
+ #endif
}
TIFF_Manager & tiff = this->tiffMgr; // Give the compiler help in recognizing non-aliases.
@@ -264,7 +285,7 @@ void TIFF_MetaHandler::ProcessXMP()
XMP_Assert ( this->containsXMP );
// Common code takes care of packetInfo.charForm, .padSize, and .writeable.
XMP_StringPtr packetStr = this->xmpPacket.c_str();
- XMP_StringLen packetLen = this->xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
try {
this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
} catch ( ... ) {
@@ -312,21 +333,27 @@ void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate )
if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks.
if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0;
- bool doInPlace = (oldPacketOffset != 0) && (oldPacketLength != 0); // ! Has old packet and new packet fits.
- if ( doInPlace && (this->tiffMgr.IsLegacyChanged()) ) doInPlace = false;
+ bool doInPlace = (this->xmpPacket.size() <= (size_t)this->packetInfo.length);
+ if ( this->tiffMgr.IsLegacyChanged() ) doInPlace = false;
if ( doInPlace ) {
#if GatherPerformanceData
sAPIPerf->back().extraInfo += ", TIFF in-place update";
#endif
+
+ if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) {
+ // They ought to match, cheap to be sure.
+ size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size();
+ this->xmpPacket.append ( extraSpace, ' ' );
+ }
LFA_FileRef liveFile = this->parent->fileRef;
XMP_Assert ( this->xmpPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic.
LFA_Seek ( liveFile, oldPacketOffset, SEEK_SET );
- LFA_Write ( liveFile, this->xmpPacket.c_str(), this->xmpPacket.size() );
+ LFA_Write ( liveFile, this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() );
} else {
@@ -337,10 +364,10 @@ void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate )
// Reserialize the XMP to get standard padding, PutXMP has probably done an in-place serialize.
this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
this->packetInfo.offset = kXMPFiles_UnknownOffset;
- this->packetInfo.length = this->xmpPacket.size();
- this->packetInfo.padSize = GetPacketPadSize ( this->xmpPacket.c_str(), this->xmpPacket.size() );
+ this->packetInfo.length = (XMP_Int32)this->xmpPacket.size();
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
- this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, this->xmpPacket.size(), this->xmpPacket.c_str() );
+ this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, (XMP_Uns32)this->xmpPacket.size(), this->xmpPacket.c_str() );
this->tiffMgr.UpdateFileStream ( destRef );
diff --git a/source/XMPFiles/FileHandlers/UCF_Handler.cpp b/source/XMPFiles/FileHandlers/UCF_Handler.cpp
new file mode 100644
index 0000000..e9c2219
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/UCF_Handler.cpp
@@ -0,0 +1,846 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// ===============================================================================================
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+#include "UCF_Handler.hpp"
+
+#include "zlib.h"
+
+#include <time.h>
+
+#ifdef DYNAMIC_CRC_TABLE
+ #error "unexpectedly DYNAMIC_CRC_TABLE defined."
+ //Must implement get_crc_table prior to any multi-threading (see notes there)
+#endif
+
+using namespace std;
+
+// =================================================================================================
+/// \file UCF_Handler.cpp
+/// \brief UCF handler class
+// =================================================================================================
+const XMP_Uns16 xmpFilenameLen = 21;
+const char* xmpFilename = "META-INF/metadata.xml";
+
+// =================================================================================================
+// UCF_MetaHandlerCTor
+// ====================
+XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new UCF_MetaHandler ( parent );
+} // UCF_MetaHandlerCTor
+
+// =================================================================================================
+// UCF_CheckFormat
+// ================
+// * lenght must at least be 114 bytes
+// * first bytes must be \x50\x4B\x03\x04 for *any* zip file
+// * at offset 30 it must spell "mimetype"
+
+#define MIN_UCF_LENGTH 114
+// zip minimum considerations:
+// the shortest legal zip is 100 byte:
+// 30+1* bytes file header
+//+ 0 byte content file (uncompressed)
+//+ 46+1* bytes central directory file header
+//+ 22 byte end of central directory record
+//-------
+//100 bytes
+//
+//1 byte is the shortest legal filename. anything below is no valid zip.
+//
+//==> the mandatory+first "mimetype" content file has a filename length of 8 bytes,
+// thus even if empty (arguably incorrect but tolerable),
+// the shortest legal UCF is 114 bytes (30 + 8 + 0 + 46 + 8 + 22 )
+// anything below is with certainty not a valid ucf.
+
+bool UCF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles * parent )
+{
+ // *not* using buffer functionality here, all we need
+ // to detect UCF securely is in the first 38 bytes...
+ IgnoreParam(filePath); IgnoreParam(parent); //suppress warnings
+ XMP_Assert ( format == kXMP_UCFFile ); //standard assert
+
+ XMP_Uns8 buffer[MIN_UCF_LENGTH];
+
+ LFA_Seek ( fileRef, 0, SEEK_SET );
+ if ( MIN_UCF_LENGTH != LFA_Read ( fileRef, buffer, MIN_UCF_LENGTH) ) //NO requireall (->no throw), just return false
+ return false;
+ if ( !CheckBytes ( &buffer[0], "\x50\x4B\x03\x04", 4 ) ) // "PK 03 04"
+ return false;
+ // UCF spec says: there must be a content file mimetype, and be first and be uncompressed...
+ if ( !CheckBytes ( &buffer[30], "mimetype", 8 ) )
+ return false;
+
+ //////////////////////////////////////////////////////////////////////////////
+ //figure out mimetype, decide on writeability
+ // grab mimetype
+ LFA_Seek( fileRef, 18, SEEK_SET );
+ XMP_Uns32 mimeLength = LFA_ReadUns32_LE ( fileRef );
+ XMP_Uns32 mimeCompressedLength = LFA_ReadUns32_LE ( fileRef ); // must be same since uncompressed
+
+ XMP_Validate( mimeLength == mimeCompressedLength,
+ "mimetype compressed and uncompressed length differ",
+ kXMPErr_BadFileFormat );
+
+ XMP_Validate( mimeLength != 0, "0-byte mimetype", kXMPErr_BadFileFormat );
+
+ // determine writability based on mimetype
+ LFA_Seek( fileRef, 30 + 8, SEEK_SET );
+ char* mimetype = new char[ mimeLength + 1 ];
+ LFA_Read( fileRef, mimetype, mimeLength, true );
+ mimetype[mimeLength] = '\0';
+
+ bool okMimetype;
+
+ // be lenient on extraneous CR (0xA) [non-XMP bug #16980028]
+ if ( mimeLength > 0 ) //avoid potential crash (will properly fail below anyhow)
+ if ( mimetype[mimeLength-1] == 0xA )
+ mimetype[mimeLength-1] = '\0';
+
+ if (
+ XMP_LitMatch( mimetype, "application/vnd.adobe.xfl" ) || //Flash Diesel team
+ XMP_LitMatch( mimetype, "application/vnd.adobe.xfl+zip") || //Flash Diesel team
+ XMP_LitMatch( mimetype, "application/vnd.adobe.x-mars" ) || //Mars plugin(labs only), Acrobat8
+ XMP_LitMatch( mimetype, "application/vnd.adobe.pdfxml" ) || //Mars plugin(labs only), Acrobat 9
+ XMP_LitMatch( mimetype, "vnd.adobe.x-asnd" ) || //Adobe Sound Document (Soundbooth Team)
+ XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-idml-package" ) ) //inCopy (inDesign) IDML Document
+
+ // *** ==> unknown are also treated as not acceptable
+ okMimetype = true;
+ else
+ okMimetype = false;
+
+ // not accepted (neither read nor write
+ //.air - Adobe Air Files
+ //application/vnd.adobe.air-application-installer-package+zip
+ //.airi - temporary Adobe Air Files
+ //application/vnd.adobe.air-application-intermediate-package+zip
+
+ delete mimetype;
+ return okMimetype;
+
+} // UCF_CheckFormat
+
+// =================================================================================================
+// UCF_MetaHandler::UCF_MetaHandler
+// ==================================
+
+UCF_MetaHandler::UCF_MetaHandler ( XMPFiles * _parent )
+{
+ this->parent = _parent;
+ this->handlerFlags = kUCF_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+} // UCF_MetaHandler::UCF_MetaHandler
+
+// =================================================================================================
+// UCF_MetaHandler::~UCF_MetaHandler
+// =====================================
+
+UCF_MetaHandler::~UCF_MetaHandler()
+{
+ // nothing
+}
+
+// =================================================================================================
+// UCF_MetaHandler::CacheFileData
+// ===============================
+//
+void UCF_MetaHandler::CacheFileData()
+{
+ //*** abort procedures
+ this->containsXMP = false; //assume no XMP for now (beware of exceptions...)
+ LFA_FileRef file = this->parent->fileRef;
+ XMP_PacketInfo &packetInfo = this->packetInfo;
+
+ // clear file positioning info ---------------------------------------------------
+ b=0;b2=0;x=0;x2=0;cd=0;cd2=0;cdx=0;cdx2=0;h=0;h2=0,fl=0;f2l=0;
+ al=0;bl=0;xl=0;x2l=0;cdl=0;cd2l=0;cdxl=0;cdx2l=0;hl=0,z=0,z2=0,z2l=0;
+ numCF=0;numCF2=0;
+ wasCompressed = false;
+
+ // -------------------------------------------------------------------------------
+ fl=LFA_Measure( file );
+ if ( fl < MIN_UCF_LENGTH ) XMP_Throw("file too short, can't be correct UCF",kXMPErr_Unimplemented);
+
+ //////////////////////////////////////////////////////////////////////////////
+ // find central directory before optional comment
+ // things have to go bottom-up, since description headers are allowed in UCF
+ // "scan backwards" until feasible field found (plus sig sanity check)
+ // OS buffering should be smart enough, so not doing anything on top
+ // plus almost all comments will be zero or rather short
+
+ //no need to check anything but the 21 chars of "METADATA-INF/metadata.xml"
+ char filenameToTest[22];
+ filenameToTest[21]='\0';
+
+ XMP_Int32 zipCommentLen = 0;
+ for ( ; zipCommentLen <= EndOfDirectory::COMMENT_MAX; zipCommentLen++ )
+ {
+ LFA_Seek( file, -zipCommentLen -2, SEEK_END );
+ if ( LFA_ReadUns16_LE( file ) == zipCommentLen ) //found it?
+ {
+ //double check, might just look like comment length (actually be 'evil' comment)
+ LFA_Seek( file , - EndOfDirectory::FIXED_SIZE, SEEK_CUR );
+ if ( LFA_ReadUns32_LE( file ) == EndOfDirectory::ID ) break; //heureka, directory ID
+ // 'else': pretend nothing happended, just go on
+ }
+ }
+ //was it a break or just not found ?
+ if ( zipCommentLen > EndOfDirectory::COMMENT_MAX ) XMP_Throw( "zip broken near end or invalid comment" , kXMPErr_BadFileFormat );
+
+ ////////////////////////////////////////////////////////////////////////////
+ //read central directory
+ hl = zipCommentLen + EndOfDirectory::FIXED_SIZE;
+ h = fl - hl;
+ LFA_Seek( file , h , SEEK_SET );
+
+ if ( LFA_ReadUns32_LE( file ) != EndOfDirectory::ID )
+ XMP_Throw("directory header id not found. or broken comment",kXMPErr_BadFileFormat);
+ if ( LFA_ReadUns16_LE( file ) != 0 )
+ XMP_Throw("UCF must be 'first' zip volume",kXMPErr_BadFileFormat);
+ if ( LFA_ReadUns16_LE( file ) != 0 )
+ XMP_Throw("UCF must be single-volume zip",kXMPErr_BadFileFormat);
+
+ numCF = LFA_ReadUns16_LE( file ); //number of content files
+ if ( numCF != LFA_ReadUns16_LE( file ) )
+ XMP_Throw( "per volume and total number of dirs differ" , kXMPErr_BadFileFormat );
+ cdl = LFA_ReadUns32_LE( file );
+ cd = LFA_ReadUns32_LE( file );
+ LFA_Seek( file, 2,SEEK_CUR ); //skip comment len, needed since next LFA is SEEK_CUR !
+
+ //////////////////////////////////////////////////////////////////////////////
+ // check for zip64-end-of-CD-locator/ zip64-end-of-CD
+ // to to central directory
+ if ( cd == 0xffffffff )
+ { // deal with zip 64, otherwise continue
+ XMP_Int64 tmp = LFA_Seek( file,
+ - EndOfDirectory::FIXED_SIZE - Zip64Locator::TOTAL_SIZE,
+ SEEK_CUR ); //go to begining of zip64 locator
+ //relative movement , absolute would imho only require another -zipCommentLen
+
+ if ( Zip64Locator::ID == LFA_ReadUns32_LE(file) ) // prevent 'coincidental length' ffffffff
+ {
+ XMP_Validate( 0 == LFA_ReadUns32_LE(file),
+ "zip64 CD disk must be 0", kXMPErr_BadFileFormat );
+
+ z = LFA_ReadUns64_LE(file);
+ XMP_Validate( z < 0xffffffffffffLL, "file in terrabyte range?", kXMPErr_BadFileFormat ); // 3* ffff, sanity test
+
+ XMP_Uns32 totalNumOfDisks = LFA_ReadUns32_LE(file);
+ /* tolerated while pkglib bug #1742179 */
+ XMP_Validate( totalNumOfDisks == 0 || totalNumOfDisks == 1,
+ "zip64 total num of disks must be 0", kXMPErr_BadFileFormat );
+
+ ///////////////////////////////////////////////
+ /// on to end-of-CD itself
+ LFA_Seek( file, z, SEEK_SET );
+ XMP_Validate( Zip64EndOfDirectory::ID == LFA_ReadUns32_LE(file),
+ "invalid zip64 end of CD sig", kXMPErr_BadFileFormat );
+
+ XMP_Int64 sizeOfZip64EOD = LFA_ReadUns64_LE(file);
+ LFA_Seek(file, 12, SEEK_CUR );
+ //yes twice "total" and "per disk"
+ XMP_Int64 tmp64 = LFA_ReadUns64_LE(file);
+ XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (1)", kXMPErr_BadFileFormat );
+ tmp64 = LFA_ReadUns64_LE(file);
+ XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (2)", kXMPErr_BadFileFormat );
+ // cd length verification
+ tmp64 = LFA_ReadUns64_LE(file);
+ XMP_Validate( tmp64 == cdl, "CD length differs in zip64", kXMPErr_BadFileFormat );
+
+ cd = LFA_ReadUns64_LE(file); // wipe out invalid 0xffffffff with the real thing
+ //ignoring "extensible data sector (would need fullLength - fixed length) for now
+ }
+ } // of zip64 fork
+ /////////////////////////////////////////////////////////////////////////////
+ // parse central directory
+ // 'foundXMP' <=> cdx != 0
+
+ LFA_Seek( file, cd, SEEK_SET );
+ XMP_Int64 cdx_suspect=0;
+ XMP_Int64 cdxl_suspect=0;
+ CDFileHeader curCDHeader;
+
+ for ( XMP_Uns16 entryNum=1 ; entryNum <= numCF ; entryNum++ )
+ {
+ cdx_suspect = LFA_Tell( file ); //just suspect for now
+ curCDHeader.read( file );
+
+ if ( GetUns32LE( &curCDHeader.fields[CDFileHeader::o_sig] ) != 0x02014b50 )
+ XMP_Throw("&invalid file header",kXMPErr_BadFileFormat);
+
+ cdxl_suspect = curCDHeader.FIXED_SIZE +
+ GetUns16LE(&curCDHeader.fields[CDFileHeader::o_fileNameLength]) +
+ GetUns16LE(&curCDHeader.fields[CDFileHeader::o_extraFieldLength]) +
+ GetUns16LE(&curCDHeader.fields[CDFileHeader::o_commentLength]);
+
+ // we only look 21 characters, that's META-INF/metadata.xml, no \0 attached
+ if ( curCDHeader.filenameLen == xmpFilenameLen /*21*/ )
+ if( XMP_LitNMatch( curCDHeader.filename , "META-INF/metadata.xml", 21 ) )
+ {
+ cdx = cdx_suspect;
+ cdxl = cdxl_suspect;
+ break;
+ }
+ //hop to next
+ LFA_Seek( file, cdx_suspect + cdxl_suspect , SEEK_SET );
+ } //for-loop, iterating *all* central directory headers (also beyond found)
+
+ if ( !cdx ) // not found xmp
+ {
+ // b and bl remain 0, x and xl remain 0
+ // ==> a is everything before directory
+ al = cd;
+ return;
+ }
+
+ // from here is if-found-only
+ //////////////////////////////////////////////////////////////////////////////
+ //CD values needed, most serve counter-validation purposes (below) only
+ // read whole object (incl. all 3 fields) again properly
+ // to get extra Fields, etc
+ LFA_Seek( file, cdx, SEEK_SET );
+ xmpCDHeader.read( file );
+
+ XMP_Validate( xmpFilenameLen == GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_fileNameLength]),
+ "content file length not ok", kXMPErr_BadFileFormat );
+
+ XMP_Uns16 CD_compression = GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_compression] );
+ XMP_Validate(( CD_compression == 0 || CD_compression == 0x08),
+ "illegal compression, must be flate or none", kXMPErr_BadFileFormat );
+ XMP_Uns16 CD_flags = GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_flags] );
+ XMP_Uns32 CD_crc = GetUns32LE( &xmpCDHeader.fields[CDFileHeader::o_crc32] );
+
+ // parse (actual, non-CD!) file header ////////////////////////////////////////////////
+ x = xmpCDHeader.offsetLocalHeader;
+ LFA_Seek( file , x ,SEEK_SET);
+ xmpFileHeader.read( file );
+ xl = xmpFileHeader.sizeHeader() + xmpCDHeader.sizeCompressed;
+
+ //values needed
+ XMP_Uns16 fileNameLength = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_fileNameLength] );
+ XMP_Uns16 extraFieldLength = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_extraFieldLength] );
+ XMP_Uns16 compression = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_compression] );
+ XMP_Uns32 sig = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sig] );
+ XMP_Uns16 flags = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_flags] );
+ XMP_Uns32 sizeCompressed = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeCompressed] );
+ XMP_Uns32 sizeUncompressed = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] );
+ XMP_Uns32 crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] );
+
+ // check filename
+ XMP_Validate( fileNameLength == 21, "filename size contradiction" , kXMPErr_BadFileFormat );
+ XMP_Enforce ( xmpFileHeader.filename != 0 );
+ XMP_Validate( !memcmp( "META-INF/metadata.xml", xmpFileHeader.filename , xmpFilenameLen ) , "filename is cf header is not META-INF/metadata.xml" , kXMPErr_BadFileFormat );
+
+ // deal with data descriptor if needed
+ if ( flags & FileHeader::kdataDescriptorFlag )
+ {
+ if ( sizeCompressed!=0 || sizeUncompressed!=0 || crc!=0 ) XMP_Throw("data descriptor must mean 3x zero",kXMPErr_BadFileFormat);
+ LFA_Seek( file, xmpCDHeader.sizeCompressed + fileNameLength + xmpCDHeader.extraFieldLen, SEEK_CUR); //skip actual data to get to descriptor
+ crc = LFA_ReadUns32_LE( file );
+ if ( crc == 0x08074b50 ) //data descriptor may or may not have signature (see spec)
+ {
+ crc = LFA_ReadUns32_LE( file ); //if it does, re-read
+ }
+ sizeCompressed = LFA_ReadUns32_LE( file );
+ sizeUncompressed = LFA_ReadUns32_LE( file );
+ // *** cater for zip64 plus 'streamed' data-descriptor stuff
+ }
+
+ // more integrity checks (post data descriptor handling)
+ if ( sig != 0x04034b50 ) XMP_Throw("invalid content file header",kXMPErr_BadFileFormat);
+ if ( compression != CD_compression ) XMP_Throw("compression contradiction",kXMPErr_BadFileFormat);
+ if ( sizeUncompressed != xmpCDHeader.sizeUncompressed ) XMP_Throw("contradicting uncompressed lengths",kXMPErr_BadFileFormat);
+ if ( sizeCompressed != xmpCDHeader.sizeCompressed ) XMP_Throw("contradicting compressed lengths",kXMPErr_BadFileFormat);
+ if ( sizeUncompressed == 0 ) XMP_Throw("0-byte uncompressed size", kXMPErr_BadFileFormat );
+
+ ////////////////////////////////////////////////////////////////////
+ // packet Info
+ this->packetInfo.charForm = stdCharForm;
+ this->packetInfo.writeable = false;
+ this->packetInfo.offset = kXMPFiles_UnknownOffset; // checksum!, hide position to not give funny ideas
+ this->packetInfo.length = kXMPFiles_UnknownLength;
+
+ ////////////////////////////////////////////////////////////////////
+ // prepare packet (compressed or not)
+ this->xmpPacket.erase();
+ this->xmpPacket.reserve( sizeUncompressed );
+ this->xmpPacket.append( sizeUncompressed, ' ' );
+ XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() ); // only set after reserving the space!
+
+ // go to packet offset
+ LFA_Seek ( file, x + xmpFileHeader.FIXED_SIZE + fileNameLength + extraFieldLength , SEEK_SET);
+
+ // compression fork --------------------------------------------------
+ switch (compression)
+ {
+ case 0x8: // FLATE
+ {
+ wasCompressed = true;
+ XMP_Uns32 bytesRead = 0;
+ XMP_Uns32 bytesWritten = 0; // for writing into packetString
+ const unsigned int CHUNK = 16384;
+
+ int ret;
+ unsigned int have; //added type
+ z_stream strm;
+ unsigned char in[CHUNK];
+ unsigned char out[CHUNK];
+ // does need this intermediate stage, no direct compressio to packetStr possible,
+ // since also partially filled buffers must be picked up. That's how it works.
+ // in addition: internal zlib variables might have 16 bit limits...
+
+ /* allocate inflate state */
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = 0;
+ strm.next_in = Z_NULL;
+
+ /* must use windowBits = -15, for raw inflate, no zlib header */
+ ret = inflateInit2(&strm,-MAX_WBITS);
+
+ if (ret != Z_OK)
+ XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
+
+ /* decompress until deflate stream ends or end of file */
+ do {
+ // must take care here not to read too much, thus whichever is smaller:
+ XMP_Int32 bytesRemaining = sizeCompressed - bytesRead;
+ if ( (XMP_Int32)CHUNK < bytesRemaining ) bytesRemaining = (XMP_Int32)CHUNK;
+ strm.avail_in=LFA_Read( file , in , bytesRemaining , true );
+ bytesRead += strm.avail_in; // NB: avail_in is "unsigned_int", so might be 16 bit (not harmfull)
+
+ if (strm.avail_in == 0) break;
+ strm.next_in = in;
+
+ do {
+ strm.avail_out = CHUNK;
+ strm.next_out = out;
+ ret = inflate(&strm, Z_NO_FLUSH);
+ XMP_Assert( ret != Z_STREAM_ERROR ); /* state not clobbered */
+ switch (ret)
+ {
+ case Z_NEED_DICT:
+ (void)inflateEnd(&strm);
+ XMP_Throw("zlib error: Z_NEED_DICT",kXMPErr_ExternalFailure);
+ case Z_DATA_ERROR:
+ (void)inflateEnd(&strm);
+ XMP_Throw("zlib error: Z_DATA_ERROR",kXMPErr_ExternalFailure);
+ case Z_MEM_ERROR:
+ (void)inflateEnd(&strm);
+ XMP_Throw("zlib error: Z_MEM_ERROR",kXMPErr_ExternalFailure);
+ }
+
+ have = CHUNK - strm.avail_out;
+ memcpy( (unsigned char*) packetStr + bytesWritten , out , have );
+ bytesWritten += have;
+
+ } while (strm.avail_out == 0);
+
+ /* it's done when inflate() says it's done */
+ } while (ret != Z_STREAM_END);
+
+ /* clean up and return */
+ (void)inflateEnd(&strm);
+ if (ret != Z_STREAM_END)
+ XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
+ break;
+ }
+ case 0x0: // no compression - read directly into the right place
+ {
+ wasCompressed = false;
+ XMP_Enforce( LFA_Read ( file, (char*)packetStr, sizeUncompressed, kLFA_RequireAll ) );
+ break;
+ }
+ default:
+ {
+ XMP_Throw("illegal zip compression method (not none, not flate)",kXMPErr_BadFileFormat);
+ }
+ }
+ this->containsXMP = true; // do this last, after all possible failure/execptions
+}
+
+
+// =================================================================================================
+// UCF_MetaHandler::ProcessXMP
+// ============================
+
+void UCF_MetaHandler::ProcessXMP()
+{
+ // we have no legacy, CacheFileData did all that was needed
+ // ==> default implementation is fine
+ XMPFileHandler::ProcessXMP();
+}
+
+// =================================================================================================
+// UCF_MetaHandler::UpdateFile
+// =============================
+
+// TODO: xmp packet with data descriptor
+
+void UCF_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ //sanity
+ XMP_Enforce( (x!=0) == (cdx!=0) );
+ if (!cdx)
+ xmpCDHeader.setXMPFilename(); //if new, set filename (impacts length, thus before computation)
+ if ( ! this->needsUpdate )
+ return;
+
+ // ***
+ if ( doSafeUpdate ) XMP_Throw ( "UCF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable );
+
+ LFA_FileRef file = this->parent->fileRef;
+
+ // final may mean compressed or not, whatever is to-be-embedded
+ uncomprPacketStr = xmpPacket.c_str();
+ uncomprPacketLen = (XMP_StringLen) xmpPacket.size();
+ finalPacketStr = uncomprPacketStr; // will be overriden if compressedXMP==true
+ finalPacketLen = uncomprPacketLen;
+ std::string compressedPacket; // moot if non-compressed, still here for scope reasons (having to keep a .c_str() alive)
+
+ if ( !x ) // if new XMP...
+ {
+ xmpFileHeader.clear();
+ xmpFileHeader.setXMPFilename();
+ // ZIP64 TODO: extra Fields, impact on cdxl2 and x2l
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // COMPRESSION DECISION
+
+ // for large files compression is bad:
+ // a) size of XMP becomes irrelevant on large files ==> why worry over compression ?
+ // b) more importantly: no such thing as padding possible, compression == ever changing sizes
+ // => never in-place rewrites, *ugly* performance impact on large files
+ inPlacePossible = false; //assume for now
+
+ if ( !x ) // no prior XMP? -> decide on filesize
+ compressXMP = ( fl > 1024*50 /* 100 kB */ ) ? false : true;
+ else
+ compressXMP = wasCompressed; // don't change a thing
+
+ if ( !wasCompressed && !compressXMP &&
+ ( GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] ) == uncomprPacketLen ))
+ {
+ inPlacePossible = true;
+ }
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // COMPRESS XMP
+ if ( compressXMP )
+ {
+ const unsigned int CHUNK = 16384;
+ int ret, flush;
+ unsigned int have;
+ z_stream strm;
+ unsigned char out[CHUNK];
+
+ /* allocate deflate state */
+ strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL;
+ if ( deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8 /*memlevel*/, Z_DEFAULT_STRATEGY) )
+ XMP_Throw("zlib error ",kXMPErr_ExternalFailure);
+
+ //write at once, since we got it in mem anyway:
+ strm.avail_in = uncomprPacketLen;
+ flush = Z_FINISH; // that's all, folks
+ strm.next_in = (unsigned char*) uncomprPacketStr;
+
+ do {
+ strm.avail_out = CHUNK;
+ strm.next_out = out;
+
+ ret = deflate(&strm, flush); /* no bad return value (!=0 acceptable) */
+ XMP_Enforce(ret != Z_STREAM_ERROR); /* state not clobbered */
+ //fwrite(buffer,size,count,file)
+ have = CHUNK - strm.avail_out;
+ compressedPacket.append( (const char*) out, have);
+ } while (strm.avail_out == 0);
+
+ if (ret != Z_STREAM_END)
+ XMP_Throw("zlib stream incomplete ",kXMPErr_ExternalFailure);
+ XMP_Enforce(strm.avail_in == 0); // all input will be used
+ (void)deflateEnd(&strm); //clean up (do prior to checks)
+
+ finalPacketStr = compressedPacket.c_str();
+ finalPacketLen = (XMP_StringLen)compressedPacket.size();
+ }
+
+ PutUns32LE ( uncomprPacketLen, &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] );
+ PutUns32LE ( finalPacketLen, &xmpFileHeader.fields[FileHeader::o_sizeCompressed] );
+ PutUns16LE ( compressXMP ? 8:0, &xmpFileHeader.fields[FileHeader::o_compression] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // CRC (always of uncompressed data)
+ XMP_Uns32 crc = crc32( 0 , (Bytef*)uncomprPacketStr, uncomprPacketLen );
+ PutUns32LE( crc, &xmpFileHeader.fields[FileHeader::o_crc32] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // TIME calculation for timestamp
+ // will be applied both to xmp content file and CD header
+ XMP_Uns16 lastModTime, lastModDate;
+ XMP_DateTime time;
+ SXMPUtils::CurrentDateTime( &time );
+
+ if ( (time.year - 1900) < 80)
+ {
+ lastModTime = 0; // 1.1.1980 00:00h
+ lastModDate = 21;
+ }
+
+ // typedef unsigned short ush; //2 bytes
+ lastModDate = (XMP_Uns16) (((time.year) - 1980 ) << 9 | ((time.month) << 5) | time.day);
+ lastModTime = ((XMP_Uns16)time.hour << 11) | ((XMP_Uns16)time.minute << 5) | ((XMP_Uns16)time.second >> 1);
+
+ PutUns16LE ( lastModDate, &xmpFileHeader.fields[FileHeader::o_lastmodDate] );
+ PutUns16LE ( lastModTime, &xmpFileHeader.fields[FileHeader::o_lastmodTime] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // adjustments depending on 4GB Border,
+ // decisions on in-place update
+ // so far only z, zl have been determined
+
+ // Zip64 related assurances, see (15)
+ XMP_Enforce(!z2);
+ XMP_Enforce(h+hl == fl );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // COMPUTE MISSING VARIABLES
+ // A - based on xmp existence
+ //
+ // already known: x, xl, cd
+ // most left side vars,
+ //
+ // finalPacketStr, finalPacketLen
+
+ if ( x ) // previous xmp?
+ {
+ al = x;
+ b = x + xl;
+ bl = cd - b;
+ }
+ else
+ {
+ al = cd;
+ //b,bl left at zero
+ }
+
+ if ( inPlacePossible )
+ { // leave xmp right after A
+ x2 = al;
+ x2l = xmpFileHeader.sizeTotalCF(); //COULDDO: assert (x2l == xl)
+ if (b) b2 = x2 + x2l; // b follows x as last content part
+ cd2 = b2 + bl; // CD follows B2
+ }
+ else
+ { // move xmp to end
+ if (b) b2 = al; // b follows
+ // x follows as last content part (B existing or not)
+ x2 = al + bl;
+ x2l = xmpFileHeader.sizeTotalCF();
+ cd2 = x2 + x2l; // CD follows X
+ }
+
+ /// create new XMP header ///////////////////////////////////////////////////
+ // written into actual fields + generation of extraField at .write()-time...
+ // however has impact on .size() computation -- thus enter before cdx2l computation
+ xmpCDHeader.sizeUncompressed = uncomprPacketLen;
+ xmpCDHeader.sizeCompressed = finalPacketLen;
+ xmpCDHeader.offsetLocalHeader = x2;
+ PutUns32LE ( crc, &xmpCDHeader.fields[CDFileHeader::o_crc32] );
+ PutUns16LE ( compressXMP ? 8:0, &xmpCDHeader.fields[CDFileHeader::o_compression] );
+ PutUns16LE ( lastModDate, &xmpCDHeader.fields[CDFileHeader::o_lastmodDate] );
+ PutUns16LE ( lastModTime, &xmpCDHeader.fields[CDFileHeader::o_lastmodTime] );
+
+ // for
+ if ( inPlacePossible )
+ {
+ cdx2 = cdx; //same, same
+ writeOut( file, file, false, true );
+ return;
+ }
+
+ ////////////////////////////////////////////////////////////////////////
+ // temporarily store (those few, small) trailing things that might not survive the move around:
+ LFA_Seek(file, cd, SEEK_SET); // seek to central directory
+ cdEntries.clear(); //mac precaution
+
+ //////////////////////////////////////////////////////////////////////////////
+ // parse headers
+ // * stick together output header list
+ cd2l = 0; //sum up below
+
+ CDFileHeader tempHeader;
+ for( XMP_Uns16 pos=1 ; pos <= numCF ; pos++ )
+ {
+ if ( (cdx) && (LFA_Tell( file ) == cdx) )
+ {
+ tempHeader.read( file ); //read, even if not use, to advance file pointer
+ }
+ else
+ {
+ tempHeader.read( file );
+ // adjust b2 offset for files that were behind the xmp:
+ // may (if xmp moved to back)
+ // or may not (inPlace Update) make a difference
+ if ( (x) && ( tempHeader.offsetLocalHeader > x) ) // if xmp existed before and this was a file behind it
+ tempHeader.offsetLocalHeader += b2 - b;
+ cd2l += tempHeader.size(); // prior offset change might have impact
+ cdEntries.push_back( tempHeader );
+ }
+ }
+
+ //push in XMP packet as last one (new or not)
+ cdEntries.push_back( xmpCDHeader );
+ cdx2l = xmpCDHeader.size();
+ cd2l += cdx2l; // true, no matter which order
+
+ //OLD cd2l = : cdl - cdxl + cdx2l; // (NB: cdxl might be 0)
+ numCF2 = numCF + ( (cdx)?0:1 ); //xmp packet for the first time? -> add one more CF
+
+ XMP_Validate( numCF2 > 0, "no content files", kXMPErr_BadFileFormat );
+ XMP_Validate( numCF2 <= 0xFFFE, "max number of 0xFFFE entries reached", kXMPErr_BadFileFormat );
+
+ cdx2 = cd2 + cd2l - cdx2l; // xmp content entry comes last (since beyond inPlace Update)
+
+ // zip64 decision
+ if ( ( cd2 + cd2l + hl ) > 0xffffffff ) // predict non-zip size ==> do we need a zip-64?
+ {
+ z2 = cd2 + cd2l;
+ z2l = Zip64EndOfDirectory::FIXED_SIZE + Zip64Locator::TOTAL_SIZE;
+ }
+
+ // header and output length,
+ h2 = cd2 + cd2l + z2l; // (z2l might be 0)
+ f2l = h2 + hl;
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // read H (endOfCD), correct offset
+ LFA_Seek(file, h, SEEK_SET);
+
+ endOfCD.read( file );
+ if ( cd2 <= 0xffffffff )
+ PutUns32LE( (XMP_Int32) cd2 , &endOfCD.fields[ endOfCD.o_CdOffset ] );
+ else
+ PutUns32LE( 0xffffffff , &endOfCD.fields[ endOfCD.o_CdOffset ] );
+ PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesDisk ] );
+ PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesTotal ] );
+
+ XMP_Enforce( cd2l <= 0xffffffff ); // _size_ of directory itself certainly under 4GB
+ PutUns32LE( (XMP_Uns32)cd2l, &endOfCD.fields[ endOfCD.o_CdSize ] );
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////
+ // MOVING
+ writeOut( file, file, false, false );
+
+ this->needsUpdate = false; //do last for safety reasons
+} // UCF_MetaHandler::UpdateFile
+
+// =================================================================================================
+// UCF_MetaHandler::WriteFile
+// ============================
+void UCF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+ IgnoreParam ( sourcePath );
+ XMP_Throw ( "UCF_MetaHandler::WriteFile: TO BE IMPLEMENTED", kXMPErr_Unimplemented );
+}
+
+// =================================================================================================
+// own approach to unify Update and WriteFile:
+// ============================
+
+void UCF_MetaHandler::writeOut( LFA_FileRef sourceFile, LFA_FileRef targetFile, bool isRewrite, bool isInPlace)
+{
+ // isInPlace is only possible when it's not a complete rewrite
+ XMP_Enforce( (!isInPlace) || (!isRewrite) );
+
+ /////////////////////////////////////////////////////////
+ // A
+ if (isRewrite) //move over A block
+ LFA_Move( sourceFile , 0 , targetFile, 0 , al );
+
+ /////////////////////////////////////////////////////////
+ // B / X (not necessarily in this order)
+ if ( !isInPlace ) // B does not change a thing (important optimization)
+ {
+ LFA_Seek( targetFile , b2 , SEEK_SET );
+ LFA_Move( sourceFile , b , targetFile, b2 , bl );
+ }
+
+ LFA_Seek( targetFile , x2 , SEEK_SET );
+ xmpFileHeader.write( targetFile );
+ LFA_Write( targetFile, finalPacketStr, finalPacketLen );
+ //TODO: cover reverse case / inplace ...
+
+ /////////////////////////////////////////////////////////
+ // CD
+ // No Seek here on purpose.
+ // This assert must still be valid
+
+ // if inPlace, the only thing that needs still correction is the CRC in CDX:
+ if ( isInPlace )
+ {
+ XMP_Uns32 crc; //TEMP, not actually needed
+ crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] );
+
+ // go there,
+ // do the job (take value directly from (non-CD-)fileheader),
+ // end of story.
+ LFA_Seek( targetFile , cdx2 + CDFileHeader::o_crc32 , SEEK_SET );
+ LFA_Write( targetFile, &xmpFileHeader.fields[FileHeader::o_crc32], 4);
+
+ return;
+ }
+
+ LFA_Seek( targetFile , cd2 , SEEK_SET );
+
+ std::vector<CDFileHeader>::iterator iter;
+ int tmptmp=1;
+ for( iter = cdEntries.begin(); iter != cdEntries.end(); iter++ ) {
+ CDFileHeader* p=&(*iter);
+ XMP_Int64 before = LFA_Tell(targetFile);
+ p->write( targetFile );
+ XMP_Int64 total = LFA_Tell(targetFile) - before;
+ XMP_Int64 tmpSize = p->size();
+ tmptmp++;
+ }
+
+ /////////////////////////////////////////////////////////
+ // Z
+ if ( z2 ) // yes, that simple
+ {
+ XMP_Assert( z2 == LFA_Tell(targetFile));
+ LFA_Seek( targetFile , z2 , SEEK_SET );
+
+ //no use in copying, always construct from scratch
+ Zip64EndOfDirectory zip64EndOfDirectory( cd2, cd2l, numCF2) ;
+ Zip64Locator zip64Locator( z2 );
+
+ zip64EndOfDirectory.write( targetFile );
+ zip64Locator.write( targetFile );
+ }
+
+ /////////////////////////////////////////////////////////
+ // H
+ XMP_Assert( h2 == LFA_Tell(targetFile));
+ endOfCD.write( targetFile );
+
+ XMP_Assert( f2l == LFA_Tell(targetFile));
+ if ( f2l< fl)
+ LFA_Truncate(targetFile,f2l); //file may have shrunk
+}
+
+
diff --git a/source/XMPFiles/FileHandlers/UCF_Handler.hpp b/source/XMPFiles/FileHandlers/UCF_Handler.hpp
new file mode 100644
index 0000000..ec60f86
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/UCF_Handler.hpp
@@ -0,0 +1,716 @@
+#ifndef __UCF_Handler_hpp__
+#define __UCF_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMPFiles_Impl.hpp"
+
+// =================================================================================================
+/// \file UCF_Handler.hpp
+//
+// underlying math:
+// __ 0 ______ 0 ______ __
+// | A | | A |
+// | | | |
+// al | | (a2l)| |
+// x |------| b2 |------|
+// xl | X | | B |_
+// b |------| (b2l)| | |
+// | B | x2 |------| | B2 could also be
+// bl | | x2l | X2 | | _after_ X2
+// cd |------| cd2 |------|<'
+// |//CD//| |//CD2/|
+// cdx |------| cdx2 |------|
+// cdxl|------| cdx2l|------|
+// cdl|//////| cd2l|//////|
+// z |------| z2|------|
+// | | | |
+// [zl]| Z | z2l | Z2 |
+// h |------| h2 |------|
+// fl | H | | H2 | f2l
+// __ hl |______| (h2l)|______| __
+//
+// fl file length pre (2 = post)
+// numCf number of content files prior to injection
+// numCf2 " post
+//
+//
+// l length variable, all else offset
+// [ ] variable is not needed
+// ( ) variable is identical to left
+// a content files prior to xmp (possibly: all)
+// b content files behind xmp (possibly: 0)
+// x xmp packet (possibly: 0)
+// cd central directory
+// h end of central directory
+//
+// z zip64 record and locator (if existing)
+//
+// general rules:
+// the bigger A, the less rewrite effort.
+// (also within the CD)
+// putting XMP at the end maximizes A.
+//
+// bool previousXMP == x!=0
+//
+// (x==0) == (cdx==0) == (xl==0) == (cdxl==0)
+//
+// std::vector<XMP_Uns32> cdOffsetsPre;
+//
+// -----------------
+// asserts:
+//( 1) a == a2 == 0, making these variables obsolete
+//( 2) a2l == al, this block is not touched
+//( 3) b2 <= b, b is only moved closer to the beginning of file
+//( 4) b2l == bl, b does not change in size
+//( 5) x2 >= x, b is only moved further down in the file
+//
+//( 6) x != 0, x2l != 0, cd != 0, cdl != 0
+// none of these blocks is at the beginning ('mimetype' by spec),
+// nor is any of them zero byte long
+//( 7) h!=0, hl >= 22 header is not at the beginning, minimum size 22
+//
+// file size computation:
+//( 8) al + bl + xl +cdl +hl = fl
+//( 9) al + bl + x2l+cd2l+hl = fl2
+//
+//(10) ( x==0 ) <=> ( cdx == 0 )
+// if there's a packet in the pre-file, or there isn't
+//(11) (x==0) => xl=0
+//(12) (cdx==0)=> cdx=0
+//
+//(13) x==0 ==> b,bl,b2,b2l==0
+// if there is no pre-xmp, B does not exist
+//(14) x!=0 ==> al:=x, b:=x+xl, bl:=cd-b
+//
+// zip 64:
+//(15) zl and z2l are basically equal, except _one_ of them is 0 :
+//
+//(16) b2l is indeed never different t
+//
+// FIXED_SIZE means the fixed (minimal) portion of a struct
+// TOTAL_SIZE indicates, that this struct indeed has a fixed, known total length
+//
+// =================================================================================================
+
+extern XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool UCF_CheckFormat ( XMP_FileFormat format,
+ XMP_StringPtr filePath,
+ LFA_FileRef fileRef,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kUCF_HandlerFlags = (
+ kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ /* kXMPFiles_PrefersInPlace | removed, only reasonable for formats where difference is significant */
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ // *** kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_NeedsReadOnlyPacket //UCF/zip has checksums...
+ );
+
+enum { // data descriptor
+ // may or may not have a signature: 0x08074b50
+ kUCF_DD_crc32 = 0,
+ kUCF_DD_sizeCompressed = 4,
+ kUCF_DD_sizeUncompressed = 8,
+};
+
+class UCF_MetaHandler : public XMPFileHandler
+{
+public:
+ UCF_MetaHandler ( XMPFiles * _parent );
+ ~UCF_MetaHandler();
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath );
+
+protected:
+ const static XMP_Uns16 xmpFilenameLen = 21;
+ const static char* xmpFilename;
+
+private:
+ class Zip64EndOfDirectory {
+ private:
+
+ public:
+ const static XMP_Uns16 o_sig = 0; // 0x06064b50
+ const static XMP_Uns16 o_size = 4; // of this, excluding leading 12 bytes
+ // == FIXED_SIZE -12, since we're never creating the extensible data sector...
+ const static XMP_Uns16 o_VersionMade = 12;
+ const static XMP_Uns16 o_VersionNeededExtr = 14;
+ const static XMP_Uns16 o_numDisk = 16; // force 0
+ const static XMP_Uns16 o_numCDDisk = 20; // force 0
+ const static XMP_Uns16 o_numCFsThisDisk = 24;
+ const static XMP_Uns16 o_numCFsTotal = 32; // force equal
+ const static XMP_Uns16 o_sizeOfCD = 40; // (regular one, not Z64)
+ const static XMP_Uns16 o_offsetCD = 48; // "
+
+ const static XMP_Int32 FIXED_SIZE = 56;
+ char fields[FIXED_SIZE];
+
+ const static XMP_Uns32 ID = 0x06064b50;
+
+ Zip64EndOfDirectory( XMP_Int64 offsetCD, XMP_Int64 sizeOfCD, XMP_Uns64 numCFs )
+ {
+ memset(fields,'\0',FIXED_SIZE);
+
+ PutUns32LE(ID ,&fields[o_sig] );
+ PutUns64LE(FIXED_SIZE - 12, &fields[o_size] ); //see above
+ PutUns16LE( 45 ,&fields[o_VersionMade] );
+ PutUns16LE( 45 ,&fields[o_VersionNeededExtr] );
+ // fine at 0: o_numDisk
+ // fine at 0: o_numCDDisk
+ PutUns64LE( numCFs, &fields[o_numCFsThisDisk] );
+ PutUns64LE( numCFs, &fields[o_numCFsTotal] );
+ PutUns64LE( sizeOfCD, &fields[o_sizeOfCD] );
+ PutUns64LE( offsetCD, &fields[o_offsetCD] );
+ }
+
+ void write(LFA_FileRef file)
+ {
+ XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );
+ LFA_Write( file , fields , FIXED_SIZE );
+ }
+
+ };
+
+ class Zip64Locator {
+ public:
+ const static XMP_Uns16 o_sig = 0; // 0x07064b50
+ const static XMP_Uns16 o_numDiskZ64CD = 4; // force 0
+ const static XMP_Uns16 o_offsZ64EOD = 8;
+ const static XMP_Uns16 o_numDisks = 16; // set 1, tolerate 0
+
+ const static XMP_Int32 TOTAL_SIZE = 20;
+ char fields[TOTAL_SIZE];
+
+ const static XMP_Uns32 ID = 0x07064b50;
+
+ Zip64Locator( XMP_Int64 offsetZ64EOD )
+ {
+ memset(fields,'\0',TOTAL_SIZE);
+ PutUns32LE(ID, &fields[Zip64Locator::o_sig] );
+ PutUns32LE(0, &fields[Zip64Locator::o_numDiskZ64CD] );
+ PutUns64LE(offsetZ64EOD, &fields[Zip64Locator::o_offsZ64EOD] );
+ PutUns32LE(1, &fields[Zip64Locator::o_numDisks] );
+ }
+
+ // writes structure to file (starting at current position)
+ void write(LFA_FileRef file)
+ {
+ XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );
+ LFA_Write( file , fields , TOTAL_SIZE );
+ }
+ };
+
+ struct EndOfDirectory {
+ public:
+ const static XMP_Int32 FIXED_SIZE = 22; //32 bit type is important to not overrun on maxcomment
+ const static XMP_Uns32 ID = 0x06054b50;
+ const static XMP_Int32 COMMENT_MAX = 0xFFFF;
+ //offsets
+ const static XMP_Int32 o_CentralDirectorySize = 12;
+ const static XMP_Int32 o_CentralDirectoryOffset = 16;
+ };
+
+ class FileHeader {
+ private:
+ //TODO intergrate in clear()
+ void release() // avoid terminus free() since subject to a #define (mem-leak-check)
+ {
+ if (filename) delete filename;
+ if (extraField) delete extraField;
+ filename=0;
+ extraField=0;
+ }
+
+ public:
+ const static XMP_Uns32 SIG = 0x04034b50;
+ const static XMP_Uns16 kdataDescriptorFlag = 0x8;
+
+ const static XMP_Uns16 o_sig = 0;
+ const static XMP_Uns16 o_extractVersion = 4;
+ const static XMP_Uns16 o_flags = 6;
+ const static XMP_Uns16 o_compression = 8;
+ const static XMP_Uns16 o_lastmodTime = 10;
+ const static XMP_Uns16 o_lastmodDate = 12;
+ const static XMP_Uns16 o_crc32 = 14;
+ const static XMP_Uns16 o_sizeCompressed = 18;
+ const static XMP_Uns16 o_sizeUncompressed = 22;
+ const static XMP_Uns16 o_fileNameLength = 26;
+ const static XMP_Uns16 o_extraFieldLength = 28;
+ // total 30
+
+ const static int FIXED_SIZE = 30;
+ char fields[FIXED_SIZE];
+
+ char* filename;
+ char* extraField;
+ XMP_Uns16 filenameLen;
+ XMP_Uns16 extraFieldLen;
+
+ void clear()
+ {
+ this->release();
+ memset(fields,'\0',FIXED_SIZE);
+ //arm with minimal default values:
+ PutUns32LE(0x04034b50, &fields[FileHeader::o_sig] );
+ PutUns16LE(0x14, &fields[FileHeader::o_extractVersion] );
+ }
+
+ FileHeader() : filename(0),filenameLen(0),extraField(0),extraFieldLen(0)
+ {
+ clear();
+ };
+
+ // reads entire *FileHeader* structure from file (starting at current position)
+ void read(LFA_FileRef file)
+ {
+ this->release();
+
+ LFA_Read( file , fields , FIXED_SIZE , true );
+
+ XMP_Uns32 tmp32 = GetUns32LE( &this->fields[FileHeader::o_sig] );
+ XMP_Validate( SIG == tmp32, "invalid header", kXMPErr_BadFileFormat );
+ filenameLen = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] );
+ extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] );
+
+ // nb unlike the CDFileHeader the FileHeader will in practice never have
+ // extra fields. Reasoning: File headers never carry (their own) offsets,
+ // (un)compressed size of XMP will hardly ever reach 4 GB
+
+ if (filenameLen) {
+ filename = new char[filenameLen];
+ LFA_Read(file,filename,filenameLen,true);
+ }
+ if (extraFieldLen) {
+ extraField = new char[extraFieldLen];
+ LFA_Read(file,extraField,extraFieldLen,true);
+ // *** NB: this WOULD need parsing for content files that are
+ // compressed or uncompressed >4GB (VERY unlikely for XMP)
+ }
+ }
+
+ // writes structure to file (starting at current position)
+ void write(LFA_FileRef file)
+ {
+ XMP_Validate( SIG == GetUns32LE( &this->fields[FileHeader::o_sig] ), "invalid header on write", kXMPErr_BadFileFormat );
+
+ filenameLen = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] );
+ extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] );
+
+ LFA_Write( file , fields , FIXED_SIZE );
+ if (filenameLen) LFA_Write( file, filename, filenameLen );
+ if (extraFieldLen) LFA_Write( file, extraField,extraFieldLen );
+ }
+
+ void transfer(const FileHeader &orig)
+ {
+ memcpy(fields,orig.fields,FIXED_SIZE);
+ if (orig.extraField)
+ {
+ extraFieldLen=orig.extraFieldLen;
+ extraField = new char[extraFieldLen];
+ memcpy(extraField,orig.extraField,extraFieldLen);
+ }
+ if (orig.filename)
+ {
+ filenameLen=orig.filenameLen;
+ filename = new char[filenameLen];
+ memcpy(filename,orig.filename,filenameLen);
+ }
+ };
+
+ void setXMPFilename()
+ {
+ // only needed for fresh structs, thus enforcing rather than catering to memory issues
+ XMP_Enforce( (filenameLen==0) && (extraFieldLen == 0) );
+ filenameLen = xmpFilenameLen;
+ PutUns16LE(filenameLen, &fields[FileHeader::o_fileNameLength] );
+ filename = new char[xmpFilenameLen];
+ memcpy(filename,"META-INF/metadata.xml",xmpFilenameLen);
+ }
+
+ XMP_Uns32 sizeHeader()
+ {
+ return this->FIXED_SIZE + this->filenameLen + this->extraFieldLen;
+ }
+
+ XMP_Uns32 sizeTotalCF()
+ {
+ //*** not zip64 bit safe yet, use only for non-large xmp packet
+ return this->sizeHeader() + GetUns32LE( &fields[FileHeader::o_sizeCompressed] );
+ }
+
+ ~FileHeader()
+ {
+ this->release();
+ };
+
+ }; //class FileHeader
+
+ ////// yes, this needs an own class
+ ////// offsets must be extracted, added, modified,
+ ////// come&go depending on being >0xffffff
+ ////class extraField {
+ //// private:
+
+
+ class CDFileHeader {
+ private:
+ void release() //*** needed or can go?
+ {
+ if (filename) delete filename;
+ if (extraField) delete extraField;
+ if (comment) delete comment;
+ filename=0; filenameLen=0;
+ extraField=0; extraFieldLen=0;
+ comment=0; commentLen=0;
+ }
+
+ const static XMP_Uns32 SIG = 0x02014b50;
+
+ public:
+ const static XMP_Uns16 o_sig = 0; //0x02014b50
+ const static XMP_Uns16 o_versionMadeBy = 4;
+ const static XMP_Uns16 o_extractVersion = 6;
+ const static XMP_Uns16 o_flags = 8;
+ const static XMP_Uns16 o_compression = 10;
+ const static XMP_Uns16 o_lastmodTime = 12;
+ const static XMP_Uns16 o_lastmodDate = 14;
+ const static XMP_Uns16 o_crc32 = 16;
+ const static XMP_Uns16 o_sizeCompressed = 20; // 16bit stub
+ const static XMP_Uns16 o_sizeUncompressed = 24; // 16bit stub
+ const static XMP_Uns16 o_fileNameLength = 28;
+ const static XMP_Uns16 o_extraFieldLength = 30;
+ const static XMP_Uns16 o_commentLength = 32;
+ const static XMP_Uns16 o_diskNo = 34;
+ const static XMP_Uns16 o_internalAttribs = 36;
+ const static XMP_Uns16 o_externalAttribs = 38;
+ const static XMP_Uns16 o_offsetLocalHeader = 42; // 16bit stub
+ // total size is 4+12+12+10+8=46
+
+ const static int FIXED_SIZE = 46;
+ char fields[FIXED_SIZE];
+
+ // do not bet on any zero-freeness,
+ // certainly no zero termination (pascal strings),
+ // treat as data blocks
+ char* filename;
+ char* extraField;
+ char* comment;
+ XMP_Uns16 filenameLen;
+ XMP_Uns16 extraFieldLen;
+ XMP_Uns16 commentLen;
+
+ // full, real, parsed 64 bit values
+ XMP_Int64 sizeUncompressed;
+ XMP_Int64 sizeCompressed;
+ XMP_Int64 offsetLocalHeader;
+
+ CDFileHeader() : filename(0),extraField(0),comment(0),filenameLen(0),
+ extraFieldLen(0),commentLen(0),sizeUncompressed(0),sizeCompressed(0),offsetLocalHeader(0)
+ {
+ memset(fields,'\0',FIXED_SIZE);
+ //already arm with appropriate values where applicable:
+ PutUns32LE(0x02014b50, &fields[CDFileHeader::o_sig] );
+ PutUns16LE(0x14, &fields[CDFileHeader::o_extractVersion] );
+ };
+
+ // copy constructor
+ CDFileHeader(const CDFileHeader& orig) : filename(0),extraField(0),comment(0),filenameLen(0),
+ extraFieldLen(0),commentLen(0),sizeUncompressed(0),sizeCompressed(0),offsetLocalHeader(0)
+ {
+ memcpy(fields,orig.fields,FIXED_SIZE);
+ if (orig.extraField)
+ {
+ extraFieldLen=orig.extraFieldLen;
+ extraField = new char[extraFieldLen];
+ memcpy(extraField , orig.extraField , extraFieldLen);
+ }
+ if (orig.filename)
+ {
+ filenameLen=orig.filenameLen;
+ filename = new char[filenameLen];
+ memcpy(filename , orig.filename , filenameLen);
+ }
+ if (orig.comment)
+ {
+ commentLen=orig.commentLen;
+ comment = new char[commentLen];
+ memcpy(comment , orig.comment , commentLen);
+ }
+
+ filenameLen = orig.filenameLen;
+ extraFieldLen = orig.extraFieldLen;
+ commentLen = orig.commentLen;
+
+ sizeUncompressed = orig.sizeUncompressed;
+ sizeCompressed = orig.sizeCompressed;
+ offsetLocalHeader = orig.offsetLocalHeader;
+ }
+
+ // Assignment operator
+ CDFileHeader& operator=(const CDFileHeader& obj)
+ {
+ XMP_Throw("not supported",kXMPErr_Unimplemented);
+ }
+
+ // reads entire structure from file (starting at current position)
+ void read(LFA_FileRef file)
+ {
+ this->release();
+
+ LFA_Read(file,fields,FIXED_SIZE,true);
+ XMP_Validate( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ), "invalid header", kXMPErr_BadFileFormat );
+
+ filenameLen = GetUns16LE( &this->fields[CDFileHeader::o_fileNameLength] );
+ extraFieldLen = GetUns16LE( &this->fields[CDFileHeader::o_extraFieldLength] );
+ commentLen = GetUns16LE( &this->fields[CDFileHeader::o_commentLength] );
+
+ if (filenameLen) {
+ filename = new char[filenameLen];
+ LFA_Read(file,filename,filenameLen,true);
+ }
+ if (extraFieldLen) {
+ extraField = new char[extraFieldLen];
+ LFA_Read(file,extraField,extraFieldLen,true);
+ }
+ if (commentLen) {
+ comment = new char[commentLen];
+ LFA_Read(file,comment,commentLen,true);
+ }
+
+ ////// GET ACTUAL 64 BIT VALUES //////////////////////////////////////////////
+ // get 32bit goodies first, correct later
+ sizeUncompressed = GetUns32LE( &fields[o_sizeUncompressed] );
+ sizeCompressed = GetUns32LE( &fields[o_sizeCompressed] );
+ offsetLocalHeader = GetUns32LE( &fields[o_offsetLocalHeader] );
+
+ XMP_Int32 offset = 0;
+ while ( offset < extraFieldLen )
+ {
+ XMP_Validate( (extraFieldLen - offset) >= 4, "need 4 bytes for next header ID+len", kXMPErr_BadFileFormat);
+ XMP_Uns16 headerID = GetUns16LE( &extraField[offset] );
+ XMP_Uns16 dataSize = GetUns16LE( &extraField[offset+2] );
+ offset += 4;
+
+ XMP_Validate( (extraFieldLen - offset) <= dataSize,
+ "actual field lenght not given", kXMPErr_BadFileFormat);
+ if ( headerID == 0x1 ) //we only care about "Zip64 extended information extra field"
+ {
+ XMP_Validate( offset < extraFieldLen, "extra field too short", kXMPErr_BadFileFormat);
+ if (sizeUncompressed == 0xffffffff)
+ {
+ sizeUncompressed = GetUns64LE( &extraField[offset] );
+ offset += 8;
+ }
+ if (sizeCompressed == 0xffffffff)
+ {
+ sizeCompressed = GetUns64LE( &extraField[offset] );
+ offset += 8;
+ }
+ if (offsetLocalHeader == 0xffffffff)
+ {
+ offsetLocalHeader = GetUns64LE( &extraField[offset] );
+ offset += 8;
+ }
+ }
+ else
+ {
+ offset += dataSize;
+ } // if
+ } // while
+ } // read()
+
+ // writes structure to file (starting at current position)
+ void write(LFA_FileRef file)
+ {
+ //// WRITE BACK REAL 64 BIT VALUES, CREATE EXTRA FIELD ///////////////
+ //may only wipe extra field after obtaining all Info from it
+ if (extraField) delete extraField;
+ extraFieldLen=0;
+
+ if ( ( sizeUncompressed > 0xffffffff ) ||
+ ( sizeCompressed > 0xffffffff ) ||
+ ( offsetLocalHeader > 0xffffffff ) )
+ {
+ extraField = new char[64]; // actual maxlen is 32
+ extraFieldLen = 4; //first fields are for ID, size
+ if ( sizeUncompressed > 0xffffffff )
+ {
+ PutUns64LE( sizeUncompressed, &extraField[extraFieldLen] );
+ extraFieldLen += 8;
+ sizeUncompressed = 0xffffffff;
+ }
+ if ( sizeCompressed > 0xffffffff )
+ {
+ PutUns64LE( sizeCompressed, &extraField[extraFieldLen] );
+ extraFieldLen += 8;
+ sizeCompressed = 0xffffffff;
+ }
+ if ( offsetLocalHeader > 0xffffffff )
+ {
+ PutUns64LE( offsetLocalHeader, &extraField[extraFieldLen] );
+ extraFieldLen += 8;
+ offsetLocalHeader = 0xffffffff;
+ }
+
+ //write ID, dataSize
+ PutUns16LE( 0x0001, &extraField[0] );
+ PutUns16LE( extraFieldLen-4, &extraField[2] );
+ //extraFieldSize
+ PutUns16LE( extraFieldLen, &this->fields[CDFileHeader::o_extraFieldLength] );
+ }
+
+ // write out 32-bit ('ff-stubs' or not)
+ PutUns32LE( (XMP_Uns32)sizeUncompressed, &fields[o_sizeUncompressed] );
+ PutUns32LE( (XMP_Uns32)sizeCompressed, &fields[o_sizeCompressed] );
+ PutUns32LE( (XMP_Uns32)offsetLocalHeader, &fields[o_offsetLocalHeader] );
+
+ /// WRITE /////////////////////////////////////////////////////////////////
+ XMP_Enforce( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ) );
+
+ LFA_Write( file , fields , FIXED_SIZE );
+ if (filenameLen) LFA_Write( file, filename , filenameLen );
+ if (extraFieldLen) LFA_Write( file, extraField , extraFieldLen );
+ if (commentLen) LFA_Write( file, extraField , extraFieldLen );
+ }
+
+ void setXMPFilename()
+ {
+ if (filename) delete filename;
+ filenameLen = xmpFilenameLen;
+ filename = new char[xmpFilenameLen];
+ PutUns16LE(filenameLen, &fields[CDFileHeader::o_fileNameLength] );
+ memcpy(filename,"META-INF/metadata.xml",xmpFilenameLen);
+ }
+
+ XMP_Int64 size()
+ {
+ XMP_Int64 r = this->FIXED_SIZE + this->filenameLen + this->commentLen;
+ // predict serialization size
+ if ( (sizeUncompressed > 0xffffffff)||(sizeCompressed > 0xffffffff)||(offsetLocalHeader>0xffffffff) )
+ {
+ r += 4; //extra fields necessary
+ if (sizeUncompressed > 0xffffffff) r += 8;
+ if (sizeCompressed > 0xffffffff) r += 8;
+ if (offsetLocalHeader > 0xffffffff) r += 8;
+ }
+ return r;
+ }
+
+ ~CDFileHeader()
+ {
+ this->release();
+ };
+ }; // class CDFileHeader
+
+ class EndOfCD {
+ private:
+ const static XMP_Uns32 SIG = 0x06054b50;
+ void UCFECD_Free()
+ {
+ if(commentLen) delete comment;
+ commentLen = 0;
+ }
+ public:
+ const static XMP_Int32 o_Sig = 0;
+ const static XMP_Int32 o_CdNumEntriesDisk = 8; // same-same for UCF, since single-volume
+ const static XMP_Int32 o_CdNumEntriesTotal = 10;// must update both
+ const static XMP_Int32 o_CdSize = 12;
+ const static XMP_Int32 o_CdOffset = 16;
+ const static XMP_Int32 o_CommentLen = 20;
+
+ const static int FIXED_SIZE = 22;
+ char fields[FIXED_SIZE];
+
+ char* comment;
+ XMP_Uns16 commentLen;
+
+ EndOfCD() : comment(0), commentLen(0)
+ {
+ //nothing
+ };
+
+ void read (LFA_FileRef file)
+ {
+ UCFECD_Free();
+
+ LFA_Read(file,fields,FIXED_SIZE,true);
+ XMP_Validate( this->SIG == GetUns32LE( &this->fields[o_Sig] ), "invalid header", kXMPErr_BadFileFormat );
+
+ commentLen = GetUns16LE( &this->fields[o_CommentLen] );
+ if(commentLen)
+ {
+ comment = new char[commentLen];
+ LFA_Read(file,comment,commentLen,true);
+ }
+ };
+
+ void write(LFA_FileRef file)
+ {
+ XMP_Enforce( this->SIG == GetUns32LE( &this->fields[o_Sig] ) );
+ commentLen = GetUns16LE( &this->fields[o_CommentLen] );
+ LFA_Write( file , fields , FIXED_SIZE );
+ if (commentLen)
+ LFA_Write ( file, comment, commentLen );
+ }
+
+ ~EndOfCD()
+ {
+ if (comment) delete comment;
+ };
+ }; //class EndOfCD
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // EMBEDDING MATH
+ //
+ // a = content files before xmp (always 0 thus ommited)
+ // b/b2 = content files behind xmp (before/after injection)
+ // x/x2 = offset xmp content header + content file (before/after injection)
+ // cd/cd = central directory
+ // h/h2 = end of central directory record
+ XMP_Int64 b,b2,x,x2,cd,cd2,cdx,cdx2,z,z2,h,h2,
+ // length thereof ('2' only where possibly different)
+ // using XMP_Int64 here also for length (not XMP_Int32),
+ // to be prepared for zip64, our LFA functions might need things in multiple chunks...
+ al,bl,xl,x2l,cdl,cd2l,cdxl,cdx2l,z2l,hl,fl,f2l;
+ XMP_Uns16 numCF,numCF2;
+
+ bool wasCompressed; // ..before, false if no prior xmp
+ bool compressXMP; // compress this time?
+ bool inPlacePossible;
+ /* bool isZip64; <=> z2 != 0 */
+
+ FileHeader xmpFileHeader;
+ CDFileHeader xmpCDHeader;
+
+ XMP_StringPtr uncomprPacketStr;
+ XMP_StringLen uncomprPacketLen;
+ XMP_StringPtr finalPacketStr;
+ XMP_StringLen finalPacketLen;
+ std::vector<CDFileHeader> cdEntries;
+ EndOfCD endOfCD;
+ void writeOut( LFA_FileRef sourceFile, LFA_FileRef targetFile, bool isRewrite, bool isInPlace);
+
+}; // UCF_MetaHandler
+
+// =================================================================================================
+
+#endif /* __UCF_Handler_hpp__ */
+
+
diff --git a/source/XMPFiles/FileHandlers/WAV_Handler.cpp b/source/XMPFiles/FileHandlers/WAV_Handler.cpp
index 1cd5eb7..6820d01 100644
--- a/source/XMPFiles/FileHandlers/WAV_Handler.cpp
+++ b/source/XMPFiles/FileHandlers/WAV_Handler.cpp
@@ -1,13 +1,16 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
-#if WIN_ENV
+#include "XMP_Environment.h" // ! This must be the first include.
+#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed.
+
+#if XMP_WinBuild
#pragma warning ( disable : 4996 ) // '...' was declared deprecated
#endif
@@ -103,7 +106,7 @@ using namespace std;
static inline int GetStringRiffSize ( const std::string & str )
{
- int l = strlen ( const_cast<char *> (str.data()) );
+ int l = (int)strlen ( const_cast<char *> (str.data()) );
if ( l & 1 ) ++l;
return l;
}
@@ -194,7 +197,7 @@ void WAV_MetaHandler::UpdateFile ( bool doSafeUpdate )
std::string strTitle, strArtist, strComment, strCopyright, strCreateDate,
strEngineer, strGenre, strAlbum, strSoftware;
-
+
if ( fReconciliate ) {
// Get the legacy item values, create the new digest, and add the digest to the XMP. The
@@ -227,7 +230,7 @@ void WAV_MetaHandler::UpdateFile ( bool doSafeUpdate )
PrepareLegacyExport ( kXMP_NS_DM, kGenre, wavInfoGenreChunk, &strGenre, &digestStr, &md5Ctx );
PrepareLegacyExport ( kXMP_NS_DM, kAlbum, wavInfoAlbumChunk, &strAlbum, &digestStr, &md5Ctx );
PrepareLegacyExport ( kXMP_NS_XMP, kSoftware, wavInfoSoftwareChunk, &strSoftware, &digestStr, &md5Ctx );
-
+
// Finish the digest and add it to the XMP.
MD5Final ( md5Val, &md5Ctx );
@@ -239,10 +242,11 @@ void WAV_MetaHandler::UpdateFile ( bool doSafeUpdate )
digestStr += hexDigits [byte & 0xF];
}
- XMP_StringLen oldLen = this->xmpPacket.size();
+ XMP_StringLen oldLen = (XMP_StringLen)this->xmpPacket.size();
this->xmpObj.SetProperty ( kXMP_NS_WAV, "NativeDigest", digestStr.c_str() );
try {
- this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_ExactPacketLength, oldLen );
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength),
+ oldLen );
} catch ( ... ) {
this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
}
@@ -250,29 +254,33 @@ void WAV_MetaHandler::UpdateFile ( bool doSafeUpdate )
}
XMP_StringPtr packetStr = this->xmpPacket.c_str();
- XMP_StringLen packetLen = this->xmpPacket.size();
+ XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
if ( packetLen == 0 ) return;
// Make sure we're writing an even number of bytes as required by the RIFF specification
- if ( (this->xmpPacket.size() & 1) == 1 ) this->xmpPacket.push_back (' ');
+ if ( (this->xmpPacket.size() & 1) == 1 )
+ this->xmpPacket.push_back (' ');
XMP_Assert ( (this->xmpPacket.size() & 1) == 0 );
packetStr = this->xmpPacket.c_str(); // ! Make sure they are current.
- packetLen = this->xmpPacket.size();
+ packetLen = (XMP_StringLen)this->xmpPacket.size();
LFA_FileRef fileRef ( this->parent->fileRef );
if ( fileRef == 0 ) return;
RIFF_Support::RiffState riffState;
- long numTags = RIFF_Support::OpenRIFF(fileRef, riffState);
+ long numTags = RIFF_Support::OpenRIFF ( fileRef, riffState );
if ( numTags == 0 ) return;
ok = RIFF_Support::PutChunk ( fileRef, riffState, formtypeWAVE, kXMPUserDataType, (char*)packetStr, packetLen );
if ( ! ok ) return;
+ ok = CreatorAtom::Update ( this->xmpObj, fileRef, formtypeWAVE, riffState );
+ if ( ! ok ) return;
+
// If needed, reconciliate the XMP data back into the native metadata.
if ( fReconciliate ) {
- PutChunk ( fileRef, riffState, wavWaveTag, wavWaveTitleChunk, strTitle.c_str(), strTitle.size() );
+ PutChunk ( fileRef, riffState, wavWaveTag, wavWaveTitleChunk, strTitle.c_str(), (XMP_Int32)strTitle.size() );
// Pad the old tags
RIFF_Support::MarkChunkAsPadding ( fileRef, riffState, 0, wavInfoCreateDateChunk, 0 );
@@ -360,14 +368,14 @@ static void AddDigestItem ( XMP_Uns32 legacyID, std::string & legacyStr, std::st
{
XMP_Uns32 leID = MakeUns32LE ( legacyID );
- XMP_Uns32 leLen = MakeUns32LE (legacyStr.size());
+ XMP_Uns32 leLen = MakeUns32LE ( (XMP_Uns32)legacyStr.size());
digestStr->append ( (char*)(&leID), 4 );
digestStr->append ( "," );
MD5Update ( md5, (XMP_Uns8*)&leID, 4 );
MD5Update ( md5, (XMP_Uns8*)&leLen, 4 );
- MD5Update ( md5, (XMP_Uns8*)legacyStr.c_str(), legacyStr.size() );
+ MD5Update ( md5, (XMP_Uns8*)legacyStr.c_str(), (XMP_Int32)legacyStr.size() );
} // AddDigestItem
@@ -409,7 +417,7 @@ static void CreateCurrentDigest ( LFA_FileRef fileRef, RIFF_Support::RiffState r
AddCurrentDigestItem ( fileRef, riffState, wavInfoGenreChunk, wavInfoTag, digestStr, &md5Ctx );
AddCurrentDigestItem ( fileRef, riffState, wavInfoAlbumChunk, wavInfoTag, digestStr, &md5Ctx );
AddCurrentDigestItem ( fileRef, riffState, wavInfoSoftwareChunk, wavInfoTag, digestStr, &md5Ctx );
-
+
MD5Final ( md5Val, &md5Ctx );
(*digestStr)[digestStr->size()-1] = ';';
@@ -434,15 +442,47 @@ void WAV_MetaHandler::CacheFileData()
bool keepExistingXMP = false; // By default an import will replace existing XMP.
bool haveLegacyItem, haveXMPItem;
- LFA_FileRef fileRef ( this->parent->fileRef );
+ LFA_FileRef fileRef ( this->parent->fileRef ); //*** simplify to assignment
+
+ bool updateFile = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate );
+ if ( updateFile ) {
+
+ // Workaround for bad files in the field that have a bad size in the outermost RIFF chunk.
+ // Repair the cases where the length is too long (beyond EOF). Don't repair a length that is
+ // less than EOF, we don't know if there actually are multiple top level chunks. There is
+ // also a check and "runtime repair" inside ReadTag, needed for read-only file access.
+
+ XMP_Int64 fileLen = LFA_Measure ( fileRef );
+ XMP_Uns32 riffLen;
+
+ LFA_Seek ( fileRef, 4, SEEK_SET );
+ LFA_Read ( fileRef, &riffLen, 4 );
+ riffLen = GetUns32LE ( &riffLen );
+
+ if ( (fileLen >= 8) && ((XMP_Int64)riffLen > (fileLen - 8)) ) { // Is the initial chunk too long?
+
+ bool repairFile = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenRepairFile );
+ if ( ! repairFile ) {
+ XMP_Throw ( "Initial RIFF tag exceeds file length", kXMPErr_BadValue );
+ } else {
+ riffLen = MakeUns32LE ( (XMP_Uns32)fileLen - 8 );
+ LFA_Seek ( fileRef, 4, SEEK_SET );
+ LFA_Write ( fileRef, &riffLen, 4 );
+ }
+
+ }
+
+ }
+
+ // Contnue with normal processing.
RIFF_Support::RiffState riffState;
long numTags = RIFF_Support::OpenRIFF ( fileRef, riffState );
- if ( numTags == 0 ) return;
+ if ( numTags == 0 ) return; //*** shouldn't we throw ? XMP_Throw("invalid file format") or such?
// Determine the size of the metadata
unsigned long bufferSize(0);
- haveLegacyItem = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType, 0, 0, 0, &bufferSize );
+ haveLegacyItem = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType /* _PMX, the xmp packet */, 0, 0, 0, &bufferSize );
if ( ! haveLegacyItem ) {
@@ -455,12 +495,13 @@ void WAV_MetaHandler::CacheFileData()
this->xmpPacket.assign(bufferSize, ' ');
// Get the metadata
- haveLegacyItem = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType, 0, 0,
- const_cast<char *>(this->xmpPacket.data()), &bufferSize );
+ XMP_Uns64 xmpPacketPosition;
+ haveLegacyItem = RIFF_Support::GetRIFFChunk ( fileRef, riffState, kXMPUserDataType /* _PMX, the xmp packet */, 0, 0,
+ const_cast<char *>(this->xmpPacket.data()), &bufferSize, &xmpPacketPosition );
if ( haveLegacyItem ) {
- this->packetInfo.offset = kXMPFiles_UnknownOffset;
+ this->packetInfo.offset = xmpPacketPosition;
this->packetInfo.length = bufferSize;
- this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), this->xmpPacket.size() );
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
this->containsXMP = true;
}
@@ -502,10 +543,24 @@ void WAV_MetaHandler::CacheFileData()
}
+ CreatorAtom::Import ( this->xmpObj, fileRef, riffState );
+
// Update the xmpPacket, as the xmpObj might have been updated with legacy info
- this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
- this->packetInfo.offset = kXMPFiles_UnknownOffset;
- this->packetInfo.length = this->xmpPacket.size();
+ if ( this->packetInfo.length == kXMPFiles_UnknownLength )
+ {
+ // kXMPFiles_UnknownLength <=> no prior packet, thus do not attempt
+ // to put in place (creates several seconds of delay
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat ) );
+ }
+ else
+ {
+ try {
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket,
+ (kXMP_UseCompactFormat | kXMP_ExactPacketLength) , this->packetInfo.length );
+ } catch ( XMP_Error ) {
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat ) );
+ }
+ }
this->processedXMP = this->containsXMP;
@@ -646,3 +701,7 @@ void WAV_MetaHandler::ImportLegacyItem ( RIFF_Support::RiffState & inOutRiffStat
}
} // WAV_MetaHandler::LoadPropertyFromRIFF
+
+// =================================================================================================
+
+#endif // XMP_UNIXBuild
diff --git a/source/XMPFiles/FileHandlers/WAV_Handler.hpp b/source/XMPFiles/FileHandlers/WAV_Handler.hpp
index a84f46b..4bd9aae 100644
--- a/source/XMPFiles/FileHandlers/WAV_Handler.hpp
+++ b/source/XMPFiles/FileHandlers/WAV_Handler.hpp
@@ -3,13 +3,16 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
+#include "XMP_Environment.h" // ! This must be the first include.
+#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed.
+
#include "XMPFiles_Impl.hpp"
#include "MD5.h"
@@ -68,4 +71,5 @@ private:
// =================================================================================================
-#endif /* __WAV_Handler_hpp__ */
+#endif // XMP_UNIXBuild
+#endif // __WAV_Handler_hpp__
diff --git a/source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp
new file mode 100644
index 0000000..47c0851
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp
@@ -0,0 +1,824 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XDCAMEX_Handler.hpp"
+#include "XDCAM_Support.hpp"
+#include "MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file XDCAMEX_Handler.cpp
+/// \brief Folder format handler for XDCAMEX.
+///
+/// This handler is for the XDCAMEX video format.
+///
+/// .../MyMovie/
+/// BPAV/
+/// MEDIAPRO.XML
+/// MEDIAPRO.BUP
+/// CLPR/
+/// 709_001_01/
+/// 709_001_01.SMI
+/// 709_001_01.MP4
+/// 709_001_01M01.XML
+/// 709_001_01R01.BIM
+/// 709_001_01I01.PPN
+/// 709_001_02/
+/// 709_002_01/
+/// 709_003_01/
+/// TAKR/
+/// 709_001/
+/// 709_001.SMI
+/// 709_001M01.XML
+///
+/// The Backup files (.BUP) are optional. No files or directories other than those listed are
+/// allowed in the BPAV directory. The CLPR (clip root) directory may contain only clip directories,
+/// which may only contain the clip files listed. The TAKR (take root) direcory may contail only
+/// take directories, which may only contain take files. The take root directory can be empty.
+/// MEDIPRO.XML contains information on clip and take management.
+///
+/// Each clip directory contains a media file (.MP4), a clip info file (.SMI), a real time metadata
+/// file (.BIM), a non real time metadata file (.XML), and a picture pointer file (.PPN). A take
+/// directory conatins a take info and non real time take metadata files.
+// =================================================================================================
+
+// =================================================================================================
+// XDCAMEX_CheckFormat
+// ===================
+//
+// This version checks for the presence of a top level BPAV directory, and the required files and
+// directories immediately within it. The CLPR and TAKR subfolders are required, as is MEDIAPRO.XML.
+//
+// The state of the string parameters depends on the form of the path passed by the client. If the
+// client passed a logical clip path, like ".../MyMovie/012_3456_01", the parameters are:
+// rootPath - ".../MyMovie"
+// gpName - empty
+// parentName - empty
+// leafName - "012_3456_01"
+// If the client passed a full file path, like ".../MyMovie/BPAV/CLPR/012_3456_01/012_3456_01M01.XML", they are:
+// rootPath - ".../MyMovie/BPAV"
+// gpName - "CLPR"
+// parentName - "012_3456_01"
+// leafName - "012_3456_01M01"
+
+// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has
+// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
+// ! file exists for a full file path.
+
+// ! Using explicit '/' as a separator when creating paths, it works on Windows.
+
+bool XDCAMEX_CheckFormat ( XMP_FileFormat format,
+ const std::string & _rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & _leafName,
+ XMPFiles * parent )
+{
+ std::string rootPath = _rootPath;
+ std::string clipName = _leafName;
+ std::string grandGPName;
+
+ std::string bpavPath ( rootPath );
+
+ // Do some initial checks on the gpName and parentName.
+
+ if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty.
+
+ if ( gpName.empty() ) {
+
+ // This is the logical clip path case. Make sure .../MyMovie/BPAV/CLPR is a folder.
+ bpavPath += kDirChar; // The rootPath was just ".../MyMovie".
+ bpavPath += "BPAV";
+ if ( GetChildMode ( bpavPath, "CLPR" ) != kFMode_IsFolder ) return false;
+
+ } else {
+
+ // This is the explicit file case. Make sure the ancestry is OK. Set the clip name from the
+ // parent folder name.
+
+ if ( gpName != "CLPR" ) return false;
+ SplitLeafName ( &rootPath, &grandGPName );
+ MakeUpperCase ( &grandGPName );
+ if ( grandGPName != "BPAV" ) return false;
+ if ( ! XMP_LitNMatch ( parentName.c_str(), clipName.c_str(), parentName.size() ) ) return false;
+
+ clipName = parentName;
+
+ }
+
+ // Check the rest of the required general structure.
+ if ( GetChildMode ( bpavPath, "TAKR" ) != kFMode_IsFolder ) return false;
+ if ( GetChildMode ( bpavPath, "MEDIAPRO.XML" ) != kFMode_IsFile ) return false;
+
+ // Make sure the clip's .MP4 and .SMI files exist.
+ std::string tempPath ( bpavPath );
+ tempPath += kDirChar;
+ tempPath += "CLPR";
+ tempPath += kDirChar;
+ tempPath += clipName;
+ tempPath += kDirChar;
+ tempPath += clipName;
+ tempPath += ".MP4";
+ if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false;
+ tempPath.erase ( tempPath.size()-3 );
+ tempPath += "SMI";
+ if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false;
+
+ // And now save the psuedo path for the handler object.
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += clipName;
+ size_t pathLen = tempPath.size() + 1; // Include a terminating nul.
+ parent->handlerTemp = malloc ( pathLen );
+ if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory );
+ memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen );
+
+ return true;
+
+} // XDCAMEX_CheckFormat
+
+// =================================================================================================
+// XDCAMEX_MetaHandlerCTor
+// =======================
+
+XMPFileHandler * XDCAMEX_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new XDCAMEX_MetaHandler ( parent );
+
+} // XDCAMEX_MetaHandlerCTor
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::XDCAMEX_MetaHandler
+// ========================================
+
+XDCAMEX_MetaHandler::XDCAMEX_MetaHandler ( XMPFiles * _parent ) : expat(0)
+{
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kXDCAMEX_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ // Extract the root path and clip name from handlerTemp.
+
+ XMP_Assert ( this->parent->handlerTemp != 0 );
+
+ this->rootPath.assign ( (char*) this->parent->handlerTemp );
+ free ( this->parent->handlerTemp );
+ this->parent->handlerTemp = 0;
+
+ SplitLeafName ( &this->rootPath, &this->clipName );
+
+} // XDCAMEX_MetaHandler::XDCAMEX_MetaHandler
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler
+// =========================================
+
+XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler()
+{
+
+ this->CleanupLegacyXML();
+ if ( this->parent->handlerTemp != 0 ) {
+ free ( this->parent->handlerTemp );
+ this->parent->handlerTemp = 0;
+ }
+
+} // XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::MakeClipFilePath
+// =====================================
+
+void XDCAMEX_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix )
+{
+
+ *path = this->rootPath;
+ *path += kDirChar;
+ *path += "BPAV";
+ *path += kDirChar;
+ *path += "CLPR";
+ *path += kDirChar;
+ *path += this->clipName;
+ *path += kDirChar;
+ *path += this->clipName;
+ *path += suffix;
+
+} // XDCAMEX_MetaHandler::MakeClipFilePath
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::MakeLegacyDigest
+// =====================================
+
+// *** Early hack version.
+
+#define kHexDigits "0123456789ABCDEF"
+
+void XDCAMEX_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ digestStr->erase();
+ if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML.
+ XMP_Assert ( this->expat != 0 );
+
+ XMP_StringPtr xdcNS = this->xdcNS.c_str();
+ XML_NodePtr legacyContext, legacyProp;
+
+ legacyContext = this->clipMetadata->GetNamedElement ( xdcNS, "Access" );
+ if ( legacyContext == 0 ) return;
+
+ MD5_CTX context;
+ unsigned char digestBin [16];
+ MD5Init ( &context );
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "Creator" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "CreationDate" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "LastUpdateDate" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->append ( buffer );
+
+} // XDCAMEX_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::CleanupLegacyXML
+// =====================================
+
+void XDCAMEX_MetaHandler::CleanupLegacyXML()
+{
+
+ if ( ! this->defaultNS.empty() ) {
+ SXMPMeta::DeleteNamespace ( this->defaultNS.c_str() );
+ this->defaultNS.erase();
+ }
+
+ if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; }
+
+ clipMetadata = 0; // ! Was a pointer into the expat tree.
+
+} // XDCAMEX_MetaHandler::CleanupLegacyXML
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::CacheFileData
+// ==================================
+
+void XDCAMEX_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) );
+
+ // See if the clip's .XMP file exists.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
+ if ( GetFileMode ( xmpPath.c_str() ) != kFMode_IsFile ) return; // No XMP.
+
+ // Read the entire .XMP file.
+
+ char openMode = 'r';
+ if ( this->parent->openFlags & kXMPFiles_OpenForUpdate ) openMode = 'w';
+
+ LFA_FileRef xmpFile = LFA_Open ( xmpPath.c_str(), openMode );
+ if ( xmpFile == 0 ) return; // The open failed.
+
+ XMP_Int64 xmpLen = LFA_Measure ( xmpFile );
+ if ( xmpLen > 100*1024*1024 ) {
+ XMP_Throw ( "XDCAMEX XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check.
+ }
+
+ this->xmpPacket.erase();
+ this->xmpPacket.reserve ( (size_t)xmpLen );
+ this->xmpPacket.append ( (size_t)xmpLen, ' ' );
+
+ XMP_Int32 ioCount = LFA_Read ( xmpFile, (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen, kLFA_RequireAll );
+ XMP_Assert ( ioCount == xmpLen );
+
+ this->packetInfo.offset = 0;
+ this->packetInfo.length = (XMP_Int32)xmpLen;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ XMP_Assert ( this->parent->fileRef == 0 );
+ if ( openMode == 'r' ) {
+ LFA_Close ( xmpFile );
+ } else {
+ this->parent->fileRef = xmpFile;
+ }
+
+ this->containsXMP = true;
+
+} // XDCAMEX_MetaHandler::CacheFileData
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::GetTakeDuration
+// ====================================
+
+void XDCAMEX_MetaHandler::GetTakeDuration ( const std::string & takeURI, std::string & duration )
+{
+
+ // Some versions of gcc can't tolerate goto's across declarations.
+ // *** Better yet, avoid this cruft with self-cleaning objects.
+ #define CleanupAndExit \
+ { \
+ if (expat != 0) delete expat; \
+ if (takeXMLFile.fileRef != 0) LFA_Close ( takeXMLFile.fileRef ); \
+ return; \
+ }
+
+ duration.clear();
+
+ // Build a directory string to the take .xml file.
+
+ std::string takeDir ( takeURI );
+ takeDir.erase ( 0, 1 ); // Change the leading "//" to "/", then all '/' to kDirChar.
+ if ( kDirChar != '/' ) {
+ for ( size_t i = 0, limit = takeDir.size(); i < limit; ++i ) {
+ if ( takeDir[i] == '/' ) takeDir[i] = kDirChar;
+ }
+ }
+
+ std::string takePath ( this->rootPath );
+ takePath += kDirChar;
+ takePath += "BPAV";
+ takePath += takeDir;
+
+ // Replace .SMI with M01.XML.
+ if ( takePath.size() > 4 ) {
+ takePath.erase ( takePath.size() - 4, 4 );
+ takePath += "M01.XML";
+ }
+
+ // Parse MEDIAPRO.XML
+
+ XML_NodePtr takeRootElem = 0;
+ XML_NodePtr context = 0;
+ AutoFile takeXMLFile;
+
+ takeXMLFile.fileRef = LFA_Open ( takePath.c_str(), 'r' );
+ if ( takeXMLFile.fileRef == 0 ) return; // The open failed.
+
+ ExpatAdapter * expat = XMP_NewExpatAdapter();
+ if ( this->expat == 0 ) return;
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = LFA_Read ( takeXMLFile.fileRef, buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+
+ expat->ParseBuffer ( 0, 0, true ); // End the parse.
+ LFA_Close ( takeXMLFile.fileRef );
+ takeXMLFile.fileRef = 0;
+
+ // Get the root node of the XML tree.
+
+ XML_Node & mediaproXMLTree = expat->tree;
+ for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) {
+ if ( mediaproXMLTree.content[i]->kind == kElemNode ) {
+ takeRootElem = mediaproXMLTree.content[i];
+ }
+ }
+ if ( takeRootElem == 0 ) CleanupAndExit
+
+ XMP_StringPtr rlName = takeRootElem->name.c_str() + takeRootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rlName, "NonRealTimeMeta" ) ) CleanupAndExit
+
+ // MediaProfile, Contents
+ XMP_StringPtr ns = takeRootElem->ns.c_str();
+ context = takeRootElem->GetNamedElement ( ns, "Duration" );
+ if ( context != 0 ) {
+ XMP_StringPtr durationValue = context->GetAttrValue ( "value" );
+ if ( durationValue != 0 ) duration = durationValue;
+ }
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+}
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::GetTakeUMID
+// ================================
+
+void XDCAMEX_MetaHandler::GetTakeUMID ( const std::string& clipUMID,
+ std::string& takeUMID,
+ std::string& takeXMLURI )
+{
+
+ // Some versions of gcc can't tolerate goto's across declarations.
+ // *** Better yet, avoid this cruft with self-cleaning objects.
+ #define CleanupAndExit \
+ { \
+ if (expat != 0) delete expat; \
+ if (mediaproXMLFile.fileRef != 0) LFA_Close ( mediaproXMLFile.fileRef ); \
+ return; \
+ }
+
+ takeUMID.clear();
+ takeXMLURI.clear();
+
+ // Build a directory string to the MEDIAPRO file.
+
+ std::string mediapropath ( this->rootPath );
+ mediapropath += kDirChar;
+ mediapropath += "BPAV";
+ mediapropath += kDirChar;
+ mediapropath += "MEDIAPRO.XML";
+
+ // Parse MEDIAPRO.XML.
+
+ XML_NodePtr mediaproRootElem = 0;
+ XML_NodePtr contentContext = 0, materialContext = 0;
+
+ AutoFile mediaproXMLFile;
+ mediaproXMLFile.fileRef = LFA_Open ( mediapropath.c_str(), 'r' );
+ if ( mediaproXMLFile.fileRef == 0 ) return; // The open failed.
+
+ ExpatAdapter * expat = XMP_NewExpatAdapter();
+ if ( this->expat == 0 ) return;
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = LFA_Read ( mediaproXMLFile.fileRef, buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+
+ expat->ParseBuffer ( 0, 0, true ); // End the parse.
+ LFA_Close ( mediaproXMLFile.fileRef );
+ mediaproXMLFile.fileRef = 0;
+
+ // Get the root node of the XML tree.
+
+ XML_Node & mediaproXMLTree = expat->tree;
+ for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) {
+ if ( mediaproXMLTree.content[i]->kind == kElemNode ) {
+ mediaproRootElem = mediaproXMLTree.content[i];
+ }
+ }
+
+ if ( mediaproRootElem == 0 ) CleanupAndExit
+ XMP_StringPtr rlName = mediaproRootElem->name.c_str() + mediaproRootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rlName, "MediaProfile" ) ) CleanupAndExit
+
+ // MediaProfile, Contents
+
+ XMP_StringPtr ns = mediaproRootElem->ns.c_str();
+ contentContext = mediaproRootElem->GetNamedElement ( ns, "Contents" );
+
+ if ( contentContext != 0 ) {
+
+ size_t numMaterialElems = contentContext->CountNamedElements ( ns, "Material" );
+
+ for ( size_t i = 0; i < numMaterialElems; ++i ) { // Iterate over Material tags.
+
+ XML_NodePtr materialElement = contentContext->GetNamedElement ( ns, "Material", i );
+ XMP_Assert ( materialElement != 0 );
+
+ XMP_StringPtr umid = materialElement->GetAttrValue ( "umid" );
+ XMP_StringPtr uri = materialElement->GetAttrValue ( "uri" );
+
+ if ( umid == 0 ) umid = "";
+ if ( uri == 0 ) uri = "";
+
+ size_t numComponents = materialElement->CountNamedElements ( ns, "Component" );
+
+ for ( size_t j = 0; j < numComponents; ++j ) {
+
+ XML_NodePtr componentElement = materialElement->GetNamedElement ( ns, "Component", j );
+ XMP_Assert ( componentElement != 0 );
+
+ XMP_StringPtr compUMID = componentElement->GetAttrValue ( "umid" );
+
+ if ( (compUMID != 0) && (compUMID == clipUMID) ) {
+ takeUMID = umid;
+ takeXMLURI = uri;
+ break;
+ }
+
+ }
+
+ if ( ! takeUMID.empty() ) break;
+
+ }
+
+ }
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+}
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::ProcessXMP
+// ===============================
+
+void XDCAMEX_MetaHandler::ProcessXMP()
+{
+
+ // Some versions of gcc can't tolerate goto's across declarations.
+ // *** Better yet, avoid this cruft with self-cleaning objects.
+ #define CleanupAndExit \
+ { \
+ bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \
+ if ( ! openForUpdate ) this->CleanupLegacyXML(); \
+ return; \
+ }
+
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ // NonRealTimeMeta -> XMP by schema.
+ std::string thisUMID, takeUMID, takeXMLURI, takeDuration;
+ std::string xmlPath;
+ this->MakeClipFilePath ( &xmlPath, "M01.XML" );
+
+ // *** Hack: Special case trickery to detect and clean up default XML namespace usage.
+
+ bool haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", 0 ); // Is there already a default namespace?
+
+ AutoFile xmlFile;
+ xmlFile.fileRef = LFA_Open ( xmlPath.c_str(), 'r' );
+ if ( xmlFile.fileRef == 0 ) return; // The open failed.
+
+ this->expat = XMP_NewExpatAdapter();
+ if ( this->expat == 0 ) XMP_Throw ( "XDCAMEX_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory );
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = LFA_Read ( xmlFile.fileRef, buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+ this->expat->ParseBuffer ( 0, 0, true ); // End the parse.
+
+ LFA_Close ( xmlFile.fileRef );
+ xmlFile.fileRef = 0;
+
+ if ( ! haveDefaultNS ) {
+ // No prior default XML namespace. If there is one now, remember it and delete it when done.
+ haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", &this->defaultNS );
+ XMP_Assert ( haveDefaultNS == (! this->defaultNS.empty()) );
+ }
+
+ // The root element should be NonRealTimeMeta in some namespace. Take whatever this file uses.
+
+ XML_Node & xmlTree = this->expat->tree;
+ XML_NodePtr rootElem = 0;
+
+ for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) {
+ if ( xmlTree.content[i]->kind == kElemNode ) rootElem = xmlTree.content[i];
+ }
+
+ if ( rootElem == 0 ) CleanupAndExit
+ XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) CleanupAndExit
+
+ this->legacyNS = rootElem->ns;
+
+ // Check the legacy digest.
+
+ XMP_StringPtr legacyNS = this->legacyNS.c_str();
+ this->clipMetadata = rootElem; // ! Save the NonRealTimeMeta pointer for other use.
+
+ std::string oldDigest, newDigest;
+ bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAMEX", &oldDigest, 0 );
+ if ( digestFound ) {
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) CleanupAndExit
+ }
+
+ // If we get here we need find and import the actual legacy elements using the current namespace.
+ // Either there is no old digest in the XMP, or the digests differ. In the former case keep any
+ // existing XMP, in the latter case take new legacy values.
+ this->containsXMP = XDCAM_Support::GetLegacyMetaData ( &this->xmpObj, rootElem, legacyNS, digestFound, thisUMID );
+
+ // If this clip is part of a take, add the take number to the relation field, and get the
+ // duration from the take metadata.
+ GetTakeUMID ( thisUMID, takeUMID, takeXMLURI );
+
+ // If this clip is part of a take, update the duration to reflect the take duration rather than
+ // the clip duration, and add the take name as a shot name.
+
+ if ( ! takeXMLURI.empty() ) {
+
+ // Update duration. This property already exists from clip legacy metadata.
+ GetTakeDuration ( takeXMLURI, takeDuration );
+ if ( ! takeDuration.empty() ) {
+ this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", takeDuration );
+ containsXMP = true;
+ }
+
+ if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "shotName" )) ) {
+
+ std::string takeName;
+ SplitLeafName ( &takeXMLURI, &takeName );
+
+ // Check for the xml suffix, and delete if it exists.
+ size_t pos = takeName.rfind(".SMI");
+ if ( pos != std::string::npos ) {
+
+ takeName.erase ( pos );
+
+ // delete the take number suffix if it exists.
+ if ( takeName.size() > 3 ) {
+
+ size_t suffix = takeName.size() - 3;
+ char c1 = takeName[suffix];
+ char c2 = takeName[suffix+1];
+ char c3 = takeName[suffix+2];
+ if ( ('U' == c1) && ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) {
+ takeName.erase ( suffix );
+ }
+
+ this->xmpObj.SetProperty ( kXMP_NS_DM, "shotName", takeName, kXMP_DeleteExisting );
+ containsXMP = true;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( (! takeUMID.empty()) &&
+ (digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "relation" ))) ) {
+ this->xmpObj.DeleteProperty ( kXMP_NS_DC, "relation" );
+ this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, takeUMID );
+ this->containsXMP = true;
+ }
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+} // XDCAMEX_MetaHandler::ProcessXMP
+
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::UpdateFile
+// ===============================
+//
+// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.
+
+void XDCAMEX_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+
+ LFA_FileRef oldFile = 0;
+ std::string filePath, tempPath;
+
+ // Update the internal legacy XML tree if we have one, and set the digest in the XMP.
+
+ bool updateLegacyXML = false;
+
+ if ( this->clipMetadata != 0 ) {
+ updateLegacyXML = XDCAM_Support::SetLegacyMetaData ( this->clipMetadata, &this->xmpObj, this->legacyNS.c_str());
+ }
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAMEX", newDigest.c_str(), kXMP_DeleteExisting );
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );
+
+ // Update the legacy XML file if necessary.
+
+ if ( updateLegacyXML ) {
+
+ std::string legacyXML;
+ this->expat->tree.Serialize ( &legacyXML );
+
+ this->MakeClipFilePath ( &filePath, "M01.XML" );
+ oldFile = LFA_Open ( filePath.c_str(), 'w' );
+
+ if ( oldFile == 0 ) {
+
+ // The XML does not exist yet.
+
+ this->MakeClipFilePath ( &filePath, "M01.XML" );
+ oldFile = LFA_Create ( filePath.c_str() );
+ if ( oldFile == 0 ) XMP_Throw ( "Failure creating XDCAMEX legacy XML file", kXMPErr_ExternalFailure );
+ LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() );
+ LFA_Close ( oldFile );
+
+ } else if ( ! doSafeUpdate ) {
+
+ // Over write the existing XML file.
+
+ LFA_Seek ( oldFile, 0, SEEK_SET );
+ LFA_Truncate ( oldFile, 0 );
+ LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() );
+ LFA_Close ( oldFile );
+
+ } else {
+
+ // Do a safe update.
+
+ // *** We really need an LFA_SwapFiles utility.
+
+ this->MakeClipFilePath ( &filePath, "M01.XML" );
+
+ CreateTempFile ( filePath, &tempPath );
+ LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' );
+ LFA_Write ( tempFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() );
+ LFA_Close ( tempFile );
+
+ LFA_Close ( oldFile );
+ LFA_Delete ( filePath.c_str() );
+ LFA_Rename ( tempPath.c_str(), filePath.c_str() );
+
+ }
+
+ }
+
+ oldFile = this->parent->fileRef;
+
+ if ( oldFile == 0 ) {
+
+ // The XMP does not exist yet.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
+
+ LFA_FileRef xmpFile = LFA_Create ( xmpPath.c_str() );
+ if ( xmpFile == 0 ) XMP_Throw ( "Failure creating XDCAMEX XMP file", kXMPErr_ExternalFailure );
+ LFA_Write ( xmpFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( xmpFile );
+
+ } else if ( ! doSafeUpdate ) {
+
+ // Over write the existing XMP file.
+
+ LFA_Seek ( oldFile, 0, SEEK_SET );
+ LFA_Truncate ( oldFile, 0 );
+ LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( oldFile );
+
+ } else {
+
+ // Do a safe update.
+
+ // *** We really need an LFA_SwapFiles utility.
+
+ std::string xmpPath, tempPath;
+
+ this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
+
+ CreateTempFile ( xmpPath, &tempPath );
+ LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' );
+ LFA_Write ( tempFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( tempFile );
+
+ LFA_Close ( oldFile );
+ LFA_Delete ( xmpPath.c_str() );
+ LFA_Rename ( tempPath.c_str(), xmpPath.c_str() );
+
+ }
+
+ this->parent->fileRef = 0;
+
+} // XDCAMEX_MetaHandler::UpdateFile
+
+// =================================================================================================
+// XDCAMEX_MetaHandler::WriteFile
+// ==============================
+
+void XDCAMEX_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+
+ // ! WriteFile is not supposed to be called for handlers that own the file.
+ XMP_Throw ( "XDCAMEX_MetaHandler::WriteFile should not be called", kXMPErr_InternalFailure );
+
+} // XDCAMEX_MetaHandler::WriteFile
+
+// =================================================================================================
diff --git a/source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp
new file mode 100644
index 0000000..63852b5
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/XDCAMEX_Handler.hpp
@@ -0,0 +1,81 @@
+#ifndef __XDCAMEX_Handler_hpp__
+#define __XDCAMEX_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles_Impl.hpp"
+
+#include "ExpatAdapter.hpp"
+
+// =================================================================================================
+/// \file XDCAMEX_Handler.hpp
+/// \brief Folder format handler for XDCAMEX.
+// =================================================================================================
+
+extern XMPFileHandler * XDCAMEX_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool XDCAMEX_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kXDCAMEX_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_FolderBasedFormat);
+
+class XDCAMEX_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files.
+ { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); };
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath );
+
+ XDCAMEX_MetaHandler ( XMPFiles * _parent );
+ virtual ~XDCAMEX_MetaHandler();
+
+private:
+
+ XDCAMEX_MetaHandler() : expat(0) {}; // Hidden on purpose.
+
+ void MakeClipFilePath ( std::string * path, XMP_StringPtr suffix );
+ void MakeLegacyDigest ( std::string * digestStr );
+
+ void GetTakeUMID( const std::string& clipUMID, std::string& takeUMID, std::string& takeXMLURI );
+ void GetTakeDuration( const std::string& takeUMID, std::string& duration );
+
+ void CleanupLegacyXML();
+
+ std::string rootPath, clipName, defaultNS, xdcNS, legacyNS, clipUMID;
+
+ ExpatAdapter * expat;
+ XML_Node * clipMetadata;
+
+}; // XDCAMEX_MetaHandler
+
+// =================================================================================================
+
+#endif /* __XDCAMEX_Handler_hpp__ */
diff --git a/source/XMPFiles/FileHandlers/XDCAM_Handler.cpp b/source/XMPFiles/FileHandlers/XDCAM_Handler.cpp
new file mode 100644
index 0000000..6ccfcab
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/XDCAM_Handler.cpp
@@ -0,0 +1,726 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XDCAM_Handler.hpp"
+#include "XDCAM_Support.hpp"
+#include "MD5.h"
+
+using namespace std;
+
+// =================================================================================================
+/// \file XDCAM_Handler.cpp
+/// \brief Folder format handler for XDCAM.
+///
+/// This handler is for the XDCAM video format. This is a pseudo-package, visible files but with a very
+/// well-defined layout and naming rules. There are 2 different layouts for XDCAM, called FAM and SAM.
+/// The FAM layout is used by "normal" XDCAM devices. The SAM layout is used by XDCAM-EX devices.
+///
+/// A typical FAM layout looks like:
+///
+/// .../MyMovie/
+/// INDEX.XML
+/// DISCMETA.XML
+/// MEDIAPRO.XML
+/// GENERAL/
+/// unknown files
+/// CLIP/
+/// C0001.MXF
+/// C0001M01.XML
+/// C0001M01.XMP
+/// C0002.MXF
+/// C0002M01.XML
+/// C0002M01.XMP
+/// SUB/
+/// C0001S01.MXF
+/// C0002S01.MXF
+/// EDIT/
+/// E0001E01.SMI
+/// E0001M01.XML
+/// E0002E01.SMI
+/// E0002M01.XML
+///
+/// A typical SAM layout looks like:
+///
+/// .../MyMovie/
+/// GENERAL/
+/// unknown files
+/// PROAV/
+/// INDEX.XML
+/// INDEX.BUP
+/// DISCMETA.XML
+/// DISCINFO.XML
+/// DISCINFO.BUP
+/// CLPR/
+/// C0001/
+/// C0001C01.SMI
+/// C0001V01.MXF
+/// C0001A01.MXF
+/// C0001A02.MXF
+/// C0001R01.BIM
+/// C0001I01.PPN
+/// C0001M01.XML
+/// C0001M01.XMP
+/// C0001S01.MXF
+/// C0002/
+/// ...
+/// EDTR/
+/// E0001/
+/// E0001E01.SMI
+/// E0001M01.XML
+/// E0002/
+/// ...
+///
+/// Note that the Sony documentation uses the folder names "General", "Clip", "Sub", and "Edit". We
+/// use all caps here. Common code has already shifted the names, we want to be case insensitive.
+///
+/// From the user's point of view, .../MyMovie contains XDCAM stuff, in this case 2 clips whose raw
+/// names are C0001 and C0002. There may be mapping information for nicer clip names to the raw
+/// names, but that can be ignored for now. Each clip is stored as a collection of files, each file
+/// holding some specific aspect of the clip's data.
+///
+/// The XDCAM handler operates on clips. The path from the client of XMPFiles can be either a logical
+/// clip path, like ".../MyMovie/C0001", or a full path to one of the files. In the latter case the
+/// handler must figure out the intended clip, it must not blindly use the named file.
+///
+/// Once the XDCAM structure and intended clip are identified, the handler only deals with the .XMP
+/// and .XML files in the CLIP or CLPR/<clip> folders. The .XMP file, if present, contains the XMP
+/// for the clip. The .XML file must be present to define the existance of the clip. It contains a
+/// variety of information about the clip, including some legacy metadata.
+///
+// =================================================================================================
+
+// =================================================================================================
+// XDCAM_CheckFormat
+// =================
+//
+// This version does fairly simple checks. The top level folder (.../MyMovie) must have exactly 1
+// child, a folder called CONTENTS. This must have a subfolder called CLIP. It may also have
+// subfolders called VIDEO, AUDIO, ICON, VOICE, and PROXY. Any mixture of these additional folders
+// is allowed, but no other children are allowed in CONTENTS. The CLIP folder must contain a .XML
+// file for the desired clip. The name checks are case insensitive.
+//
+// The state of the string parameters depends on the form of the path passed by the client. If the
+// client passed a logical clip path, like ".../MyMovie/C0001", the parameters are:
+// rootPath - ".../MyMovie"
+// gpName - empty
+// parentName - empty
+// leafName - "C0001"
+//
+// If the client passed a FAM file path, like ".../MyMovie/EDIT/E0001E01.SMI", they are:
+// rootPath - "..."
+// gpName - "MyMovie"
+// parentName - "EDIT"
+// leafName - "E0001E01"
+//
+// If the client passed a SAM file path, like ".../MyMovie/PROAV/CLPR/C0001/C0001A02.MXF", they are:
+// rootPath - ".../MyMovie/PROAV"
+// gpName - "CLPR"
+// parentName - "C0001"
+// leafName - "C0001A02"
+//
+// For both FAM and SAM the leading character of the leafName for an existing file might be coerced
+// to 'C' to form the logical clip name. And suffix such as "M01" must be removed for FAM. We don't
+// need to worry about that for SAM, that uses the <clip> folder name.
+
+// ! The FAM format supports general clip file names through an ALIAS.XML mapping file. The simple
+// ! existence check has an edge case bug, left to be fixed later. If the ALIAS.XML file exists, but
+// ! some of the clips still have "raw" names, and we're passed an existing file path in the EDIT
+// ! folder, we will fail to do the leading 'E' to 'C' coercion. We might also erroneously remove a
+// ! suffix from a mapped essence file with a name like ClipX01.MXF.
+
+// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has
+// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the
+// ! file exists for a full file path.
+
+bool XDCAM_CheckFormat ( XMP_FileFormat format,
+ const std::string & _rootPath,
+ const std::string & _gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent )
+{
+ std::string rootPath = _rootPath; // ! Need tweaking in the existing file cases (FAM and SAM).
+ std::string gpName = _gpName;
+
+ bool isFAM = false;
+
+ std::string tempPath, childName;
+ XMP_FolderInfo folderInfo;
+
+ std::string clipName = leafName;
+
+ // Do some basic checks on the root path and component names. Decide if this is FAM or SAM.
+
+ if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty.
+
+ if ( gpName.empty() ) {
+
+ // This is the logical clip path case. Just look for PROAV to see if this is FAM or SAM.
+ if ( GetChildMode ( rootPath, "PROAV" ) != kFMode_IsFolder ) isFAM = true;
+
+ } else {
+
+ // This is the existing file case. See if this is FAM or SAM, tweak the clip name as needed.
+
+ if ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) {
+ isFAM = true;
+ } else if ( (gpName != "CLPR") && (gpName != "EDTR") ) {
+ return false;
+ }
+
+ if ( isFAM ) {
+
+ // Put the proper root path together. Clean up the clip name if needed.
+
+ if ( ! rootPath.empty() ) rootPath += kDirChar;
+ rootPath += gpName;
+ gpName.erase();
+
+ if ( GetChildMode ( rootPath, "ALIAS.XML" ) != kFMode_IsFile ) {
+ clipName[0] = 'C'; // ! See notes above about pending bug.
+ }
+
+ if ( clipName.size() > 3 ) {
+ size_t clipMid = clipName.size() - 3;
+ char c1 = clipName[clipMid];
+ char c2 = clipName[clipMid+1];
+ char c3 = clipName[clipMid+2];
+ if ( ('A' <= c1) && (c1 <= 'Z') &&
+ ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) {
+ clipName.erase ( clipMid );
+ }
+ }
+
+ } else {
+
+ // Fix the clip name. Check for and strip the "PROAV" suffix on the root path.
+
+ clipName = parentName; // ! We have a folder with the (almost) exact clip name.
+ clipName[0] = 'C';
+
+ std::string proav;
+ SplitLeafName ( &rootPath, &proav );
+ MakeUpperCase ( &proav );
+ if ( (rootPath.empty()) || (proav != "PROAV") ) return false;
+
+ }
+
+ }
+
+ // Make sure the general XDCAM package structure is legit. Set tempPath as a bogus path of the
+ // form <root>/<FAM-or-SAM>/<clip>, e.g. ".../MyMovie/FAM/C0001". This is passed the handler via
+ // the handlerTemp hackery.
+
+ if ( isFAM ) {
+
+ if ( (format != kXMP_XDCAM_FAMFile) && (format != kXMP_UnknownFile) ) return false;
+
+ tempPath = rootPath;
+
+ if ( GetChildMode ( tempPath, "INDEX.XML" ) != kFMode_IsFile ) return false;
+ if ( GetChildMode ( tempPath, "DISCMETA.XML" ) != kFMode_IsFile ) return false;
+ if ( GetChildMode ( tempPath, "MEDIAPRO.XML" ) != kFMode_IsFile ) return false;
+
+ tempPath += kDirChar;
+ tempPath += "CLIP";
+ tempPath += kDirChar;
+ tempPath += clipName;
+ tempPath += "M01.XML";
+ if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false;
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "FAM";
+ tempPath += kDirChar;
+ tempPath += clipName;
+
+ } else {
+
+ if ( (format != kXMP_XDCAM_SAMFile) && (format != kXMP_UnknownFile) ) return false;
+
+ // We already know about the PROAV folder, just check below it.
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "PROAV";
+
+ if ( GetChildMode ( tempPath, "INDEX.XML" ) != kFMode_IsFile ) return false;
+ if ( GetChildMode ( tempPath, "DISCMETA.XML" ) != kFMode_IsFile ) return false;
+ if ( GetChildMode ( tempPath, "DISCINFO.XML" ) != kFMode_IsFile ) return false;
+ if ( GetChildMode ( tempPath, "CLPR" ) != kFMode_IsFolder ) return false;
+
+ tempPath += kDirChar;
+ tempPath += "CLPR";
+ tempPath += kDirChar;
+ tempPath += clipName;
+ if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFolder ) return false;
+
+ tempPath += kDirChar;
+ tempPath += clipName;
+ tempPath += "M01.XML";
+ if ( GetFileMode ( tempPath.c_str() ) != kFMode_IsFile ) return false;
+
+ tempPath = rootPath;
+ tempPath += kDirChar;
+ tempPath += "SAM";
+ tempPath += kDirChar;
+ tempPath += clipName;
+
+ }
+
+ // Save the pseudo-path for the handler object. A bit of a hack, but the only way to get info
+ // from here to there.
+
+ size_t pathLen = tempPath.size() + 1; // Include a terminating nul.
+ parent->handlerTemp = malloc ( pathLen );
+ if ( parent->handlerTemp == 0 ) XMP_Throw ( "No memory for XDCAM clip info", kXMPErr_NoMemory );
+ memcpy ( parent->handlerTemp, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above.
+
+ return true;
+
+} // XDCAM_CheckFormat
+
+// =================================================================================================
+// XDCAM_MetaHandlerCTor
+// =====================
+
+XMPFileHandler * XDCAM_MetaHandlerCTor ( XMPFiles * parent )
+{
+ return new XDCAM_MetaHandler ( parent );
+
+} // XDCAM_MetaHandlerCTor
+
+// =================================================================================================
+// XDCAM_MetaHandler::XDCAM_MetaHandler
+// ====================================
+
+XDCAM_MetaHandler::XDCAM_MetaHandler ( XMPFiles * _parent ) : isFAM(false), expat(0)
+{
+
+ this->parent = _parent; // Inherited, can't set in the prefix.
+ this->handlerFlags = kXDCAM_HandlerFlags;
+ this->stdCharForm = kXMP_Char8Bit;
+
+ // Extract the root path, clip name, and FAM/SAM flag from handlerTemp.
+
+ XMP_Assert ( this->parent->handlerTemp != 0 );
+
+ this->rootPath.assign ( (char*) this->parent->handlerTemp );
+ free ( this->parent->handlerTemp );
+ this->parent->handlerTemp = 0;
+
+ SplitLeafName ( &this->rootPath, &this->clipName );
+
+ std::string temp;
+ SplitLeafName ( &this->rootPath, &temp );
+ XMP_Assert ( (temp == "FAM") || (temp == "SAM") );
+ if ( temp == "FAM" ) this->isFAM = true;
+ XMP_Assert ( this->isFAM ? (this->parent->format == kXMP_XDCAM_FAMFile) : (this->parent->format == kXMP_XDCAM_SAMFile) );
+
+} // XDCAM_MetaHandler::XDCAM_MetaHandler
+
+// =================================================================================================
+// XDCAM_MetaHandler::~XDCAM_MetaHandler
+// =====================================
+
+XDCAM_MetaHandler::~XDCAM_MetaHandler()
+{
+
+ this->CleanupLegacyXML();
+ if ( this->parent->handlerTemp != 0 ) {
+ free ( this->parent->handlerTemp );
+ this->parent->handlerTemp = 0;
+ }
+
+} // XDCAM_MetaHandler::~XDCAM_MetaHandler
+
+// =================================================================================================
+// XDCAM_MetaHandler::MakeClipFilePath
+// ===================================
+
+void XDCAM_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix )
+{
+
+ *path = this->rootPath;
+ *path += kDirChar;
+
+ if ( this->isFAM ) {
+ *path += "CLIP";
+ } else {
+ *path += "PROAV";
+ *path += kDirChar;
+ *path += "CLPR";
+ *path += kDirChar;
+ *path += this->clipName;
+ }
+
+ *path += kDirChar;
+ *path += this->clipName;
+ *path += suffix;
+
+} // XDCAM_MetaHandler::MakeClipFilePath
+
+// =================================================================================================
+// XDCAM_MetaHandler::MakeLegacyDigest
+// ===================================
+
+// *** Early hack version.
+
+#define kHexDigits "0123456789ABCDEF"
+
+void XDCAM_MetaHandler::MakeLegacyDigest ( std::string * digestStr )
+{
+ digestStr->erase();
+ if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML.
+ XMP_Assert ( this->expat != 0 );
+
+ XMP_StringPtr xdcNS = this->xdcNS.c_str();
+ XML_NodePtr legacyContext, legacyProp;
+
+ legacyContext = this->clipMetadata->GetNamedElement ( xdcNS, "Access" );
+ if ( legacyContext == 0 ) return;
+
+ MD5_CTX context;
+ unsigned char digestBin [16];
+ MD5Init ( &context );
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "Creator" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "CreationDate" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ legacyProp = legacyContext->GetNamedElement ( xdcNS, "LastUpdateDate" );
+ if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) {
+ const XML_Node * xmlValue = legacyProp->content[0];
+ MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() );
+ }
+
+ MD5Final ( digestBin, &context );
+
+ char buffer [40];
+ for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digestBin[in];
+ buffer[out] = kHexDigits [ byte >> 4 ];
+ buffer[out+1] = kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+ digestStr->append ( buffer );
+
+} // XDCAM_MetaHandler::MakeLegacyDigest
+
+// =================================================================================================
+// P2_MetaHandler::CleanupLegacyXML
+// ================================
+
+void XDCAM_MetaHandler::CleanupLegacyXML()
+{
+
+ if ( ! this->defaultNS.empty() ) {
+ SXMPMeta::DeleteNamespace ( this->defaultNS.c_str() );
+ this->defaultNS.erase();
+ }
+
+ if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; }
+
+ clipMetadata = 0; // ! Was a pointer into the expat tree.
+
+} // XDCAM_MetaHandler::CleanupLegacyXML
+
+// =================================================================================================
+// XDCAM_MetaHandler::CacheFileData
+// ================================
+
+void XDCAM_MetaHandler::CacheFileData()
+{
+ XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) );
+
+ // See if the clip's .XMP file exists.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
+ if ( GetFileMode ( xmpPath.c_str() ) != kFMode_IsFile ) return; // No XMP.
+
+ // Read the entire .XMP file.
+
+ char openMode = 'r';
+ if ( this->parent->openFlags & kXMPFiles_OpenForUpdate ) openMode = 'w';
+
+ LFA_FileRef xmpFile = LFA_Open ( xmpPath.c_str(), openMode );
+ if ( xmpFile == 0 ) return; // The open failed.
+
+ XMP_Int64 xmpLen = LFA_Measure ( xmpFile );
+ if ( xmpLen > 100*1024*1024 ) {
+ XMP_Throw ( "XDCAM XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check.
+ }
+
+ this->xmpPacket.erase();
+ this->xmpPacket.reserve ( (size_t)xmpLen );
+ this->xmpPacket.append ( (size_t)xmpLen, ' ' );
+
+ XMP_Int32 ioCount = LFA_Read ( xmpFile, (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen, kLFA_RequireAll );
+ XMP_Assert ( ioCount == xmpLen );
+
+ this->packetInfo.offset = 0;
+ this->packetInfo.length = (XMP_Int32)xmpLen;
+ FillPacketInfo ( this->xmpPacket, &this->packetInfo );
+
+ XMP_Assert ( this->parent->fileRef == 0 );
+ if ( openMode == 'r' ) {
+ LFA_Close ( xmpFile );
+ } else {
+ this->parent->fileRef = xmpFile;
+ }
+
+ this->containsXMP = true;
+
+} // XDCAM_MetaHandler::CacheFileData
+
+// =================================================================================================
+// XDCAM_MetaHandler::ProcessXMP
+// =============================
+
+void XDCAM_MetaHandler::ProcessXMP()
+{
+
+ // Some versions of gcc can't tolerate goto's across declarations.
+ // *** Better yet, avoid this cruft with self-cleaning objects.
+ #define CleanupAndExit \
+ { \
+ bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \
+ if ( ! openForUpdate ) this->CleanupLegacyXML(); \
+ return; \
+ }
+
+ if ( this->processedXMP ) return;
+ this->processedXMP = true; // Make sure only called once.
+
+ if ( this->containsXMP ) {
+ this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() );
+ }
+
+ // NonRealTimeMeta -> XMP by schema
+ std::string xmlPath, umid;
+ this->MakeClipFilePath ( &xmlPath, "M01.XML" );
+
+ // --------------------------------------------------------------
+ // *** This is a minimal Q&D example of legacy metadata handling.
+ // *** Hack: Special case trickery to detect and clean up default XML namespace usage.
+
+ bool haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", 0 ); // Is there already a default namespace?
+
+ AutoFile xmlFile;
+ xmlFile.fileRef = LFA_Open ( xmlPath.c_str(), 'r' );
+ if ( xmlFile.fileRef == 0 ) return; // The open failed.
+
+ this->expat = XMP_NewExpatAdapter();
+ if ( this->expat == 0 ) XMP_Throw ( "XDCAM_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory );
+
+ XMP_Uns8 buffer [64*1024];
+ while ( true ) {
+ XMP_Int32 ioCount = LFA_Read ( xmlFile.fileRef, buffer, sizeof(buffer) );
+ if ( ioCount == 0 ) break;
+ this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ );
+ }
+ this->expat->ParseBuffer ( 0, 0, true ); // End the parse.
+
+ LFA_Close ( xmlFile.fileRef );
+ xmlFile.fileRef = 0;
+
+ if ( ! haveDefaultNS ) {
+ // No prior default XML namespace. If there is one now, remember it and delete it when done.
+ haveDefaultNS = SXMPMeta::GetNamespaceURI ( "_dflt_", &this->defaultNS );
+ XMP_Assert ( haveDefaultNS == (! this->defaultNS.empty()) );
+ }
+
+ // The root element should be NonRealTimeMeta in some namespace. Take whatever this file uses.
+
+ XML_Node & xmlTree = this->expat->tree;
+ XML_NodePtr rootElem = 0;
+
+ for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) {
+ if ( xmlTree.content[i]->kind == kElemNode ) {
+ rootElem = xmlTree.content[i];
+ }
+ }
+
+ if ( rootElem == 0 ) CleanupAndExit
+ XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen;
+ if ( ! XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) CleanupAndExit
+
+ this->legacyNS = rootElem->ns;
+
+ // Check the legacy digest.
+
+ XMP_StringPtr legacyNS = this->legacyNS.c_str();
+
+ this->clipMetadata = rootElem; // ! Save the NonRealTimeMeta pointer for other use.
+
+ std::string oldDigest, newDigest;
+ bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAM", &oldDigest, 0 );
+ if ( digestFound ) {
+ this->MakeLegacyDigest ( &newDigest );
+ if ( oldDigest == newDigest ) CleanupAndExit
+ }
+
+ // If we get here we need find and import the actual legacy elements using the current namespace.
+ // Either there is no old digest in the XMP, or the digests differ. In the former case keep any
+ // existing XMP, in the latter case take new legacy values.
+
+ this->containsXMP = XDCAM_Support::GetLegacyMetaData ( &this->xmpObj, rootElem, legacyNS, digestFound, umid );
+
+ CleanupAndExit
+ #undef CleanupAndExit
+
+} // XDCAM_MetaHandler::ProcessXMP
+
+// =================================================================================================
+// XDCAM_MetaHandler::UpdateFile
+// =============================
+//
+// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here.
+
+void XDCAM_MetaHandler::UpdateFile ( bool doSafeUpdate )
+{
+ if ( ! this->needsUpdate ) return;
+ this->needsUpdate = false; // Make sure only called once.
+
+ LFA_FileRef oldFile = 0;
+ std::string filePath, tempPath;
+
+ // Update the internal legacy XML tree if we have one, and set the digest in the XMP.
+
+ bool updateLegacyXML = false;
+
+ if ( this->clipMetadata != 0 ) {
+ updateLegacyXML = XDCAM_Support::SetLegacyMetaData ( this->clipMetadata, &this->xmpObj, this->legacyNS.c_str());
+ }
+
+ std::string newDigest;
+ this->MakeLegacyDigest ( &newDigest );
+ this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAM", newDigest.c_str(), kXMP_DeleteExisting );
+ this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() );
+
+ // Update the legacy XML file if necessary.
+
+ if ( updateLegacyXML ) {
+
+ std::string legacyXML;
+ this->expat->tree.Serialize ( &legacyXML );
+
+ this->MakeClipFilePath ( &filePath, "M01.XML" );
+ oldFile = LFA_Open ( filePath.c_str(), 'w' );
+
+ if ( oldFile == 0 ) {
+
+ // The XML does not exist yet.
+
+ this->MakeClipFilePath ( &filePath, "M01.XML" );
+ oldFile = LFA_Create ( filePath.c_str() );
+ if ( oldFile == 0 ) XMP_Throw ( "Failure creating XDCAMEX legacy XML file", kXMPErr_ExternalFailure );
+ LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() );
+ LFA_Close ( oldFile );
+
+ } else if ( ! doSafeUpdate ) {
+
+ // Over write the existing XML file.
+
+ LFA_Seek ( oldFile, 0, SEEK_SET );
+ LFA_Truncate ( oldFile, 0 );
+ LFA_Write ( oldFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() );
+ LFA_Close ( oldFile );
+
+ } else {
+
+ // Do a safe update.
+
+ // *** We really need an LFA_SwapFiles utility.
+
+ this->MakeClipFilePath ( &filePath, "M01.XML" );
+
+ CreateTempFile ( filePath, &tempPath );
+ LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' );
+ LFA_Write ( tempFile, legacyXML.data(), (XMP_StringLen)legacyXML.size() );
+ LFA_Close ( tempFile );
+
+ LFA_Close ( oldFile );
+ LFA_Delete ( filePath.c_str() );
+ LFA_Rename ( tempPath.c_str(), filePath.c_str() );
+
+ }
+
+ }
+
+ oldFile = this->parent->fileRef;
+
+ if ( oldFile == 0 ) {
+
+ // The XMP does not exist yet.
+
+ std::string xmpPath;
+ this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
+
+ LFA_FileRef xmpFile = LFA_Create ( xmpPath.c_str() );
+ if ( xmpFile == 0 ) XMP_Throw ( "Failure creating XDCAM XMP file", kXMPErr_ExternalFailure );
+ LFA_Write ( xmpFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( xmpFile );
+
+ } else if ( ! doSafeUpdate ) {
+
+ // Over write the existing XMP file.
+
+ LFA_Seek ( oldFile, 0, SEEK_SET );
+ LFA_Truncate ( oldFile, 0 );
+ LFA_Write ( oldFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( oldFile );
+
+ } else {
+
+ // Do a safe update.
+
+ // *** We really need an LFA_SwapFiles utility.
+
+ std::string xmpPath, tempPath;
+
+ this->MakeClipFilePath ( &xmpPath, "M01.XMP" );
+
+ CreateTempFile ( xmpPath, &tempPath );
+ LFA_FileRef tempFile = LFA_Open ( tempPath.c_str(), 'w' );
+ LFA_Write ( tempFile, this->xmpPacket.data(), (XMP_StringLen)this->xmpPacket.size() );
+ LFA_Close ( tempFile );
+
+ LFA_Close ( oldFile );
+ LFA_Delete ( xmpPath.c_str() );
+ LFA_Rename ( tempPath.c_str(), xmpPath.c_str() );
+
+ }
+
+ this->parent->fileRef = 0;
+
+} // XDCAM_MetaHandler::UpdateFile
+
+// =================================================================================================
+// XDCAM_MetaHandler::WriteFile
+// ============================
+
+void XDCAM_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
+{
+
+ // ! WriteFile is not supposed to be called for handlers that own the file.
+ XMP_Throw ( "XDCAM_MetaHandler::WriteFile should not be called", kXMPErr_InternalFailure );
+
+} // XDCAM_MetaHandler::WriteFile
+
+// =================================================================================================
diff --git a/source/XMPFiles/FileHandlers/XDCAM_Handler.hpp b/source/XMPFiles/FileHandlers/XDCAM_Handler.hpp
new file mode 100644
index 0000000..abd861b
--- /dev/null
+++ b/source/XMPFiles/FileHandlers/XDCAM_Handler.hpp
@@ -0,0 +1,82 @@
+#ifndef __XDCAM_Handler_hpp__
+#define __XDCAM_Handler_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles_Impl.hpp"
+
+#include "ExpatAdapter.hpp"
+
+// =================================================================================================
+/// \file XDCAM_Handler.hpp
+/// \brief Folder format handler for XDCAM.
+///
+/// This header ...
+///
+// =================================================================================================
+
+extern XMPFileHandler * XDCAM_MetaHandlerCTor ( XMPFiles * parent );
+
+extern bool XDCAM_CheckFormat ( XMP_FileFormat format,
+ const std::string & rootPath,
+ const std::string & gpName,
+ const std::string & parentName,
+ const std::string & leafName,
+ XMPFiles * parent );
+
+static const XMP_OptionBits kXDCAM_HandlerFlags = (kXMPFiles_CanInjectXMP |
+ kXMPFiles_CanExpand |
+ kXMPFiles_CanRewrite |
+ kXMPFiles_PrefersInPlace |
+ kXMPFiles_CanReconcile |
+ kXMPFiles_AllowsOnlyXMP |
+ kXMPFiles_ReturnsRawPacket |
+ kXMPFiles_HandlerOwnsFile |
+ kXMPFiles_AllowsSafeUpdate |
+ kXMPFiles_FolderBasedFormat);
+
+class XDCAM_MetaHandler : public XMPFileHandler
+{
+public:
+
+ void CacheFileData();
+ void ProcessXMP();
+
+ XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files.
+ { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); };
+
+ void UpdateFile ( bool doSafeUpdate );
+ void WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath );
+
+ XDCAM_MetaHandler ( XMPFiles * _parent );
+ virtual ~XDCAM_MetaHandler();
+
+private:
+
+ XDCAM_MetaHandler() : isFAM(false), expat(0), clipMetadata(0) {}; // Hidden on purpose.
+
+ void MakeClipFilePath ( std::string * path, XMP_StringPtr suffix );
+ void MakeLegacyDigest ( std::string * digestStr );
+ void CleanupLegacyXML();
+
+ std::string rootPath, clipName, defaultNS, xdcNS, legacyNS;
+
+ bool isFAM;
+
+ ExpatAdapter * expat;
+ XML_Node * clipMetadata; // ! Don't delete, points into the Expat tree.
+
+}; // XDCAM_MetaHandler
+
+// =================================================================================================
+
+#endif /* __XDCAM_Handler_hpp__ */
diff --git a/source/XMPFiles/FormatSupport/ASF_Support.cpp b/source/XMPFiles/FormatSupport/ASF_Support.cpp
new file mode 100644
index 0000000..3baa7d0
--- /dev/null
+++ b/source/XMPFiles/FormatSupport/ASF_Support.cpp
@@ -0,0 +1,1434 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "ASF_Support.hpp"
+#include "UnicodeConversions.hpp"
+
+#if XMP_WinBuild
+ #define snprintf _snprintf
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+ #pragma warning ( disable : 4267 ) // *** conversion (from size_t), possible loss of date (many 64 bit related)
+#endif
+
+// =============================================================================================
+
+// Platforms other than Win
+#if ! XMP_WinBuild
+int IsEqualGUID ( const GUID& guid1, const GUID& guid2 )
+{
+ return (memcmp ( &guid1, &guid2, sizeof(GUID) ) == 0);
+}
+#endif
+
+ASF_Support::ASF_Support() : legacyManager(0), posFileSizeInfo(0) {}
+
+ASF_Support::ASF_Support ( ASF_LegacyManager* _legacyManager ) : posFileSizeInfo(0)
+{
+ legacyManager = _legacyManager;
+}
+
+ASF_Support::~ASF_Support()
+{
+ legacyManager = 0;
+}
+
+// =============================================================================================
+
+long ASF_Support::OpenASF ( LFA_FileRef fileRef, ObjectState & inOutObjectState )
+{
+ XMP_Uns64 pos = 0;
+ XMP_Uns64 len;
+
+ try {
+ pos = LFA_Seek ( fileRef, 0, SEEK_SET );
+ } catch ( ... ) {}
+
+ if ( pos != 0 ) return 0;
+
+ // read first and following chunks
+ while ( ReadObject ( fileRef, inOutObjectState, &len, pos) ) {}
+
+ return inOutObjectState.objects.size();
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::ReadObject ( LFA_FileRef fileRef, ObjectState & inOutObjectState, XMP_Uns64 * objectLength, XMP_Uns64 & inOutPosition )
+{
+
+ try {
+
+ XMP_Uns64 startPosition = inOutPosition;
+ long bytesRead;
+ ASF_ObjectBase objectBase;
+
+ bytesRead = LFA_Read ( fileRef, &objectBase, kASF_ObjectBaseLen, true );
+ if ( bytesRead != kASF_ObjectBaseLen ) return false;
+
+ *objectLength = GetUns64LE ( &objectBase.size );
+ inOutPosition += *objectLength;
+
+ ObjectData newObject;
+
+ newObject.pos = startPosition;
+ newObject.len = *objectLength;
+ newObject.guid = objectBase.guid;
+
+ // xmpIsLastObject indicates, that the XMP-object is the last top-level object
+ // reset here, if any another object is read
+ inOutObjectState.xmpIsLastObject = false;
+
+ if ( IsEqualGUID ( ASF_Header_Object, newObject.guid ) ) {
+
+ // header object ?
+ this->ReadHeaderObject ( fileRef, inOutObjectState, newObject );
+
+ } else if ( IsEqualGUID ( ASF_XMP_Metadata, newObject.guid ) ) {
+
+ // check object for XMP GUID
+ inOutObjectState.xmpPos = newObject.pos + kASF_ObjectBaseLen;
+ inOutObjectState.xmpLen = newObject.len - kASF_ObjectBaseLen;
+ inOutObjectState.xmpIsLastObject = true;
+ inOutObjectState.xmpObject = newObject;
+ newObject.xmp = true;
+
+ }
+
+ inOutObjectState.objects.push_back ( newObject );
+
+ LFA_Seek ( fileRef, inOutPosition, SEEK_SET );
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::ReadHeaderObject ( LFA_FileRef fileRef, ObjectState& inOutObjectState, const ObjectData& newObject )
+{
+ if ( ! IsEqualGUID ( ASF_Header_Object, newObject.guid) || (! legacyManager ) ) return false;
+
+ std::string buffer;
+
+ legacyManager->SetPadding(0);
+
+ try {
+
+ // read header-object structure
+ XMP_Uns64 pos = newObject.pos;
+ XMP_Uns32 bufferSize = kASF_ObjectBaseLen + 6;
+
+ buffer.clear();
+ buffer.reserve ( bufferSize );
+ buffer.assign ( bufferSize, ' ' );
+ LFA_Seek ( fileRef, pos, SEEK_SET );
+ LFA_Read ( fileRef, const_cast<char*>(buffer.data()), bufferSize, true );
+
+ XMP_Uns64 read = bufferSize;
+ pos += bufferSize;
+
+ // read contained header objects
+ XMP_Uns32 numberOfHeaders = GetUns32LE ( &buffer[24] );
+ ASF_ObjectBase objectBase;
+
+ while ( read < newObject.len ) {
+
+ LFA_Seek ( fileRef, pos, SEEK_SET );
+ if ( kASF_ObjectBaseLen != LFA_Read ( fileRef, &objectBase, kASF_ObjectBaseLen, true ) ) break;
+
+ LFA_Seek ( fileRef, pos, SEEK_SET );
+ objectBase.size = GetUns64LE ( &objectBase.size );
+
+ if ( IsEqualGUID ( ASF_File_Properties_Object, objectBase.guid) && (objectBase.size >= 104 ) ) {
+
+ buffer.clear();
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ LFA_Read ( fileRef, const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size), true );
+
+ // save position of filesize-information
+ posFileSizeInfo = (pos + 40);
+
+ // creation date
+ std::string sub ( buffer.substr ( 48, 8 ) );
+ legacyManager->SetField ( ASF_LegacyManager::fieldCreationDate, sub );
+
+ // broadcast flag set ?
+ XMP_Uns32 flags = GetUns32LE ( &buffer[88] );
+ inOutObjectState.broadcast = (flags & 1);
+ legacyManager->SetBroadcast ( inOutObjectState.broadcast );
+
+ legacyManager->SetObjectExists ( ASF_LegacyManager::objectFileProperties );
+
+ } else if ( IsEqualGUID ( ASF_Content_Description_Object, objectBase.guid) && (objectBase.size >= 34 ) ) {
+
+ buffer.clear();
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ LFA_Read ( fileRef, const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size), true );
+
+ XMP_Uns16 titleLen = GetUns16LE ( &buffer[24] );
+ XMP_Uns16 authorLen = GetUns16LE ( &buffer[26] );
+ XMP_Uns16 copyrightLen = GetUns16LE ( &buffer[28] );
+ XMP_Uns16 descriptionLen = GetUns16LE ( &buffer[30] );
+ XMP_Uns16 ratingLen = GetUns16LE ( &buffer[32] );
+
+ XMP_Uns16 fieldPos = 34;
+
+ std::string titleStr = buffer.substr ( fieldPos, titleLen );
+ fieldPos += titleLen;
+ legacyManager->SetField ( ASF_LegacyManager::fieldTitle, titleStr );
+
+ std::string authorStr = buffer.substr ( fieldPos, authorLen );
+ fieldPos += authorLen;
+ legacyManager->SetField ( ASF_LegacyManager::fieldAuthor, authorStr );
+
+ std::string copyrightStr = buffer.substr ( fieldPos, copyrightLen );
+ fieldPos += copyrightLen;
+ legacyManager->SetField ( ASF_LegacyManager::fieldCopyright, copyrightStr );
+
+ std::string descriptionStr = buffer.substr ( fieldPos, descriptionLen );
+ fieldPos += descriptionLen;
+ legacyManager->SetField ( ASF_LegacyManager::fieldDescription, descriptionStr );
+
+ /* rating is currently not part of reconciliation
+ std::string ratingStr = buffer.substr ( fieldPos, ratingLen );
+ fieldPos += ratingLen;
+ legacyData.append ( titleStr );
+ */
+
+ legacyManager->SetObjectExists ( ASF_LegacyManager::objectContentDescription );
+
+ } else if ( IsEqualGUID ( ASF_Content_Branding_Object, objectBase.guid ) ) {
+
+ buffer.clear();
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ LFA_Read ( fileRef, const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size), true );
+
+ XMP_Uns32 fieldPos = 28;
+
+ // copyright URL is 3. element with variable size
+ for ( int i = 1; i <= 3 ; ++i ) {
+ XMP_Uns32 len = GetUns32LE ( &buffer[fieldPos] );
+ if ( i == 3 ) {
+ std::string copyrightURLStr = buffer.substr ( fieldPos + 4, len );
+ legacyManager->SetField ( ASF_LegacyManager::fieldCopyrightURL, copyrightURLStr );
+ }
+ fieldPos += (len + 4);
+ }
+
+ legacyManager->SetObjectExists ( ASF_LegacyManager::objectContentBranding );
+
+#if ! Exclude_LicenseURL_Recon
+
+ } else if ( IsEqualGUID ( ASF_Content_Encryption_Object, objectBase.guid ) ) {
+
+ buffer.clear();
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ LFA_Read ( fileRef, const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size), true );
+
+ XMP_Uns32 fieldPos = 24;
+
+ // license URL is 4. element with variable size
+ for ( int i = 1; i <= 4 ; ++i ) {
+ XMP_Uns32 len = GetUns32LE ( &buffer[fieldPos] );
+ if ( i == 4 ) {
+ std::string licenseURLStr = buffer.substr ( fieldPos + 4, len );
+ legacyManager->SetField ( ASF_LegacyManager::fieldLicenseURL, licenseURLStr );
+ }
+ fieldPos += (len + 4);
+ }
+
+ legacyManager->SetObjectExists ( objectContentEncryption );
+
+#endif
+
+ } else if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) ) {
+
+ legacyManager->SetPadding ( legacyManager->GetPadding() + (objectBase.size - 24) );
+
+ } else if ( IsEqualGUID ( ASF_Header_Extension_Object, objectBase.guid ) ) {
+
+ this->ReadHeaderExtensionObject ( fileRef, inOutObjectState, pos, objectBase );
+
+ }
+
+ pos += objectBase.size;
+ read += objectBase.size;
+ }
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ legacyManager->ComputeDigest();
+
+ return true;
+}
+
+// =============================================================================================
+
+bool ASF_Support::WriteHeaderObject ( LFA_FileRef sourceRef, LFA_FileRef destRef, const ObjectData& object, ASF_LegacyManager& _legacyManager, bool usePadding )
+{
+ if ( ! IsEqualGUID ( ASF_Header_Object, object.guid ) ) return false;
+
+ bool ret = false;
+
+ std::string buffer;
+ XMP_Uns16 valueUns16LE;
+ XMP_Uns32 valueUns32LE;
+ XMP_Uns64 valueUns64LE;
+
+ try {
+
+ // read header-object structure
+ XMP_Uns64 pos = object.pos;
+ XMP_Uns32 bufferSize = kASF_ObjectBaseLen + 6;
+
+ buffer.clear();
+ buffer.reserve ( bufferSize );
+ buffer.assign ( bufferSize, ' ' );
+ LFA_Seek ( sourceRef, pos, SEEK_SET );
+ LFA_Read ( sourceRef, const_cast<char*>(buffer.data()), bufferSize, true );
+
+ XMP_Uns64 read = bufferSize;
+ pos += bufferSize;
+
+ // read contained header objects
+ XMP_Uns32 numberOfHeaders = GetUns32LE ( &buffer[24] );
+ ASF_ObjectBase objectBase;
+
+ // prepare new header in memory
+ std::string header;
+
+ int changedObjects = _legacyManager.changedObjects();
+ int exportedObjects = 0;
+ int writtenObjects = 0;
+
+ header.append ( buffer.c_str(), bufferSize );
+
+ while ( read < object.len ) {
+
+ LFA_Seek ( sourceRef, pos, SEEK_SET );
+ if ( kASF_ObjectBaseLen != LFA_Read ( sourceRef, &objectBase, kASF_ObjectBaseLen, true ) ) break;
+
+ LFA_Seek ( sourceRef, pos, SEEK_SET );
+ objectBase.size = GetUns64LE ( &objectBase.size );
+
+ int headerStartPos = header.size();
+
+ // save position of filesize-information
+ if ( IsEqualGUID ( ASF_File_Properties_Object, objectBase.guid ) ) {
+ posFileSizeInfo = (headerStartPos + 40);
+ }
+
+ // write objects
+ if ( IsEqualGUID ( ASF_File_Properties_Object, objectBase.guid ) &&
+ (objectBase.size >= 104) && (changedObjects & ASF_LegacyManager::objectFileProperties) ) {
+
+ // copy object and replace creation-date
+ buffer.reserve ( XMP_Uns32 ( objectBase.size ) );
+ buffer.assign ( XMP_Uns32 ( objectBase.size ), ' ' );
+ LFA_Read ( sourceRef, const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size), true );
+ header.append ( buffer, 0, XMP_Uns32( objectBase.size ) );
+
+ if ( ! _legacyManager.GetBroadcast() ) {
+ buffer = _legacyManager.GetField ( ASF_LegacyManager::fieldCreationDate );
+ ReplaceString ( header, buffer, (headerStartPos + 48), 8 );
+ }
+
+ exportedObjects |= ASF_LegacyManager::objectFileProperties;
+
+ } else if ( IsEqualGUID ( ASF_Content_Description_Object, objectBase.guid ) &&
+ (objectBase.size >= 34) && (changedObjects & ASF_LegacyManager::objectContentDescription) ) {
+
+ // re-create object with xmp-data
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ LFA_Read ( sourceRef, const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size), true );
+ // write header only
+ header.append ( buffer, 0, XMP_Uns32( kASF_ObjectBaseLen ) );
+
+ // write length fields
+
+ XMP_Uns16 titleLen = _legacyManager.GetField ( ASF_LegacyManager::fieldTitle).size( );
+ valueUns16LE = MakeUns16LE ( titleLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 authorLen = _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor).size( );
+ valueUns16LE = MakeUns16LE ( authorLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 copyrightLen = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright).size( );
+ valueUns16LE = MakeUns16LE ( copyrightLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 descriptionLen = _legacyManager.GetField ( ASF_LegacyManager::fieldDescription).size( );
+ valueUns16LE = MakeUns16LE ( descriptionLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ // retrieve existing overall length of preceding fields
+ XMP_Uns16 precedingLen = 0;
+ precedingLen += GetUns16LE ( &buffer[24] ); // Title
+ precedingLen += GetUns16LE ( &buffer[26] ); // Author
+ precedingLen += GetUns16LE ( &buffer[28] ); // Copyright
+ precedingLen += GetUns16LE ( &buffer[30] ); // Description
+ // retrieve existing 'Rating' length
+ XMP_Uns16 ratingLen = GetUns16LE ( &buffer[32] ); // Rating
+ valueUns16LE = MakeUns16LE ( ratingLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ // write field contents
+
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldTitle ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldDescription ) );
+ header.append ( buffer, (34 + precedingLen), ratingLen );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ exportedObjects |= ASF_LegacyManager::objectContentDescription;
+
+ } else if ( IsEqualGUID ( ASF_Content_Branding_Object, objectBase.guid ) &&
+ (changedObjects & ASF_LegacyManager::objectContentBranding) ) {
+
+ // re-create object with xmp-data
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ LFA_Read ( sourceRef, const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size), true );
+
+ // calculate size of fields coming before 'Copyright URL'
+ XMP_Uns32 length = 28;
+ length += (GetUns32LE ( &buffer[length] ) + 4); // Banner Image Data
+ length += (GetUns32LE ( &buffer[length] ) + 4); // Banner Image URL
+
+ // write first part of header
+ header.append ( buffer, 0, length );
+
+ // copyright URL
+ length = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL).size( );
+ valueUns32LE = MakeUns32LE ( length );
+ header.append ( (const char*)&valueUns32LE, 4 );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL ) );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ exportedObjects |= ASF_LegacyManager::objectContentBranding;
+
+#if ! Exclude_LicenseURL_Recon
+
+ } else if ( IsEqualGUID ( ASF_Content_Encryption_Object, objectBase.guid ) &&
+ (changedObjects & ASF_LegacyManager::objectContentEncryption) ) {
+
+ // re-create object with xmp-data
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ LFA_Read ( sourceRef, const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size), true );
+
+ // calculate size of fields coming before 'License URL'
+ XMP_Uns32 length = 24;
+ length += (GetUns32LE ( &buffer[length] ) + 4); // Secret Data
+ length += (GetUns32LE ( &buffer[length] ) + 4); // Protection Type
+ length += (GetUns32LE ( &buffer[length] ) + 4); // Key ID
+
+ // write first part of header
+ header.append ( buffer, 0, length );
+
+ // License URL
+ length = _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL).size( );
+ valueUns32LE = MakeUns32LE ( length );
+ header.append ( (const char*)&valueUns32LE, 4 );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL ) );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ exportedObjects |= ASF_LegacyManager::objectContentEncryption;
+
+#endif
+
+ } else if ( IsEqualGUID ( ASF_Header_Extension_Object, objectBase.guid ) && usePadding ) {
+
+ // re-create object if padding needs to be used
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ LFA_Read ( sourceRef, const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size), true );
+
+ ASF_Support::WriteHeaderExtensionObject ( buffer, &header, objectBase, 0 );
+
+ } else if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) && usePadding ) {
+
+ // eliminate padding (will be created as last object)
+
+ } else {
+
+ // simply copy all other objects
+ buffer.reserve ( XMP_Uns32( objectBase.size ) );
+ buffer.assign ( XMP_Uns32( objectBase.size ), ' ' );
+ LFA_Read ( sourceRef, const_cast<char*>(buffer.data()), XMP_Int32(objectBase.size), true );
+
+ header.append ( buffer, 0, XMP_Uns32( objectBase.size ) );
+
+ }
+
+ pos += objectBase.size;
+ read += objectBase.size;
+
+ writtenObjects ++;
+
+ }
+
+ // any objects to create ?
+ int newObjects = (changedObjects ^ exportedObjects);
+
+ if ( newObjects ) {
+
+ // create new objects with xmp-data
+ int headerStartPos;
+ ASF_ObjectBase newObjectBase;
+ XMP_Uns32 length;
+
+ if ( newObjects & ASF_LegacyManager::objectContentDescription ) {
+
+ headerStartPos = header.size();
+ newObjectBase.guid = ASF_Content_Description_Object;
+ newObjectBase.size = 0;
+
+ // write object header
+ header.append ( (const char*)&newObjectBase, kASF_ObjectBaseLen );
+
+ XMP_Uns16 titleLen = _legacyManager.GetField ( ASF_LegacyManager::fieldTitle).size( );
+ valueUns16LE = MakeUns16LE ( titleLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 authorLen = _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor).size( );
+ valueUns16LE = MakeUns16LE ( authorLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 copyrightLen = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright).size( );
+ valueUns16LE = MakeUns16LE ( copyrightLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 descriptionLen = _legacyManager.GetField ( ASF_LegacyManager::fieldDescription).size( );
+ valueUns16LE = MakeUns16LE ( descriptionLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ XMP_Uns16 ratingLen = 0;
+ valueUns16LE = MakeUns16LE ( ratingLen );
+ header.append ( (const char*)&valueUns16LE, 2 );
+
+ // write field contents
+
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldTitle ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright ) );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldDescription ) );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ newObjects &= ~ASF_LegacyManager::objectContentDescription;
+
+ writtenObjects ++;
+
+ }
+
+ if ( newObjects & ASF_LegacyManager::objectContentBranding ) {
+
+ headerStartPos = header.size();
+ newObjectBase.guid = ASF_Content_Branding_Object;
+ newObjectBase.size = 0;
+
+ // write object header
+ header.append ( (const char*)&newObjectBase, kASF_ObjectBaseLen );
+
+ // write 'empty' fields
+ header.append ( 12, '\0' );
+
+ // copyright URL
+ length = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL).size( );
+ valueUns32LE = MakeUns32LE ( length );
+ header.append ( (const char*)&valueUns32LE, 4 );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL ) );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ newObjects &= ~ASF_LegacyManager::objectContentBranding;
+
+ writtenObjects ++;
+
+ }
+
+#if ! Exclude_LicenseURL_Recon
+
+ if ( newObjects & ASF_LegacyManager::objectContentEncryption ) {
+
+ headerStartPos = header.size();
+ newObjectBase.guid = ASF_Content_Encryption_Object;
+ newObjectBase.size = 0;
+
+ // write object header
+ header.append ( (const char*)&newObjectBase, kASF_ObjectBaseLen );
+
+ // write 'empty' fields
+ header.append ( 12, '\0' );
+
+ // License URL
+ length = _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL).size( );
+ valueUns32LE = MakeUns32LE ( length );
+ header.append ( (const char*)&valueUns32LE, 4 );
+ header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL ) );
+
+ // update new object size
+ valueUns64LE = MakeUns64LE ( header.size() - headerStartPos );
+ std::string newSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newSize, (headerStartPos + 16), 8 );
+
+ newObjects &= ~ASF_LegacyManager::objectContentEncryption;
+
+ writtenObjects ++;
+
+ }
+
+#endif
+
+ }
+
+ // create padding object ?
+ if ( usePadding && (header.size ( ) < object.len ) ) {
+ ASF_Support::CreatePaddingObject ( &header, (object.len - header.size()) );
+ writtenObjects ++;
+ }
+
+ // update new header-object size
+ valueUns64LE = MakeUns64LE ( header.size() );
+ std::string newValue ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( header, newValue, 16, 8 );
+
+ // update new number of Header objects
+ valueUns32LE = MakeUns32LE ( writtenObjects );
+ newValue = std::string ( (const char*)&valueUns32LE, 4 );
+ ReplaceString ( header, newValue, 24, 4 );
+
+ // if we are operating on the same file (in-place update), place pointer before writing
+ if ( sourceRef == destRef ) LFA_Seek ( destRef, object.pos, SEEK_SET );
+
+ // write header
+ LFA_Write ( destRef, header.c_str(), header.size() );
+
+ } catch ( ... ) {
+
+ ret = false;
+
+ }
+
+ return ret;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::UpdateHeaderObject ( LFA_FileRef fileRef, const ObjectData& object, ASF_LegacyManager& _legacyManager )
+{
+ return ASF_Support::WriteHeaderObject ( fileRef, fileRef, object, _legacyManager, true );
+}
+
+// =============================================================================================
+
+bool ASF_Support::UpdateFileSize ( LFA_FileRef fileRef )
+{
+ if ( fileRef == 0 ) return false;
+
+ XMP_Uns64 posCurrent = LFA_Seek ( fileRef, 0, SEEK_CUR );
+ XMP_Uns64 newSizeLE = MakeUns64LE ( LFA_Measure ( fileRef ) );
+
+ if ( this->posFileSizeInfo != 0 ) {
+
+ LFA_Seek ( fileRef, this->posFileSizeInfo, SEEK_SET );
+
+ } else {
+
+ // The position of the file size field is not known, find it.
+
+ ASF_ObjectBase objHeader;
+
+ // Read the Header object at the start of the file.
+
+ LFA_Seek ( fileRef, 0, SEEK_SET );
+ LFA_Read ( fileRef, &objHeader, kASF_ObjectBaseLen, kLFA_RequireAll );
+ if ( ! IsEqualGUID ( ASF_Header_Object, objHeader.guid ) ) return false;
+
+ XMP_Uns32 childCount;
+ LFA_Read ( fileRef, &childCount, 4, kLFA_RequireAll );
+ childCount = GetUns32LE ( &childCount );
+
+ LFA_Seek ( fileRef, 2, SEEK_CUR ); // Skip the 2 reserved bytes.
+
+ // Look for the File Properties object in the Header's children.
+
+ for ( ; childCount > 0; --childCount ) {
+ LFA_Read ( fileRef, &objHeader, kASF_ObjectBaseLen, kLFA_RequireAll );
+ if ( IsEqualGUID ( ASF_File_Properties_Object, objHeader.guid ) ) break;
+ XMP_Uns64 dataLen = GetUns64LE ( &objHeader.size ) - 24;
+ LFA_Seek ( fileRef, dataLen, SEEK_CUR ); // Skip this object's data.
+ }
+ if ( childCount == 0 ) return false;
+
+ // Seek to the file size field.
+
+ XMP_Uns64 fpoSize = GetUns64LE ( &objHeader.size );
+ if ( fpoSize < (16+8+16+8) ) return false;
+ LFA_Seek ( fileRef, 16, SEEK_CUR ); // Skip to the file size field.
+
+ }
+
+ LFA_Write ( fileRef, &newSizeLE, 8 ); // Write the new file size.
+
+ LFA_Seek ( fileRef, posCurrent, SEEK_SET );
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::ReadHeaderExtensionObject ( LFA_FileRef fileRef, ObjectState& inOutObjectState, const XMP_Uns64& _pos, const ASF_ObjectBase& _objectBase )
+{
+ if ( ! IsEqualGUID ( ASF_Header_Extension_Object, _objectBase.guid) || (! legacyManager ) ) return false;
+
+ try {
+
+ // read extended header-object structure beginning at the data part (offset = 46)
+ const XMP_Uns64 offset = 46;
+ XMP_Uns64 read = 0;
+ XMP_Uns64 data = (_objectBase.size - offset);
+ XMP_Uns64 pos = (_pos + offset);
+
+ ASF_ObjectBase objectBase;
+
+ while ( read < data ) {
+
+ LFA_Seek ( fileRef, pos, SEEK_SET );
+ if ( kASF_ObjectBaseLen != LFA_Read ( fileRef, &objectBase, kASF_ObjectBaseLen, true ) ) break;
+
+ objectBase.size = GetUns64LE ( &objectBase.size );
+
+ if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) ) {
+ legacyManager->SetPadding ( legacyManager->GetPadding() + (objectBase.size - 24) );
+ }
+
+ pos += objectBase.size;
+ read += objectBase.size;
+
+ }
+
+ } catch ( ... ) {
+
+ return false;
+
+ }
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::WriteHeaderExtensionObject ( const std::string& buffer, std::string* header, const ASF_ObjectBase& _objectBase, const int /*reservePadding*/ )
+{
+ if ( ! IsEqualGUID ( ASF_Header_Extension_Object, _objectBase.guid ) || (! header) || (buffer.size() < 46) ) return false;
+
+ const XMP_Uns64 offset = 46;
+ int startPos = header->size();
+
+ // copy header base
+ header->append ( buffer, 0, offset );
+
+ // read extended header-object structure beginning at the data part (offset = 46)
+ XMP_Uns64 read = 0;
+ XMP_Uns64 data = (_objectBase.size - offset);
+ XMP_Uns64 pos = offset;
+
+ ASF_ObjectBase objectBase;
+
+ while ( read < data ) {
+
+ memcpy ( &objectBase, &buffer[int(pos)], kASF_ObjectBaseLen );
+ objectBase.size = GetUns64LE ( &objectBase.size );
+
+ if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) ) {
+ // eliminate
+ } else {
+ // copy other objects
+ header->append ( buffer, XMP_Uns32(pos), XMP_Uns32(objectBase.size) );
+ }
+
+ pos += objectBase.size;
+ read += objectBase.size;
+
+ }
+
+ // update header extension data size
+ XMP_Uns32 valueUns32LE = MakeUns32LE ( header->size() - startPos - offset );
+ std::string newDataSize ( (const char*)&valueUns32LE, 4 );
+ ReplaceString ( *header, newDataSize, (startPos + 42), 4 );
+
+ // update new object size
+ XMP_Uns64 valueUns64LE = MakeUns64LE ( header->size() - startPos );
+ std::string newObjectSize ( (const char*)&valueUns64LE, 8 );
+ ReplaceString ( *header, newObjectSize, (startPos + 16), 8 );
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::CreatePaddingObject ( std::string* header, const XMP_Uns64 size )
+{
+ if ( ( ! header) || (size < 24) ) return false;
+
+ ASF_ObjectBase newObjectBase;
+
+ newObjectBase.guid = ASF_Padding_Object;
+ newObjectBase.size = MakeUns64LE ( size );
+
+ // write object header
+ header->append ( (const char*)&newObjectBase, kASF_ObjectBaseLen );
+
+ // write 'empty' padding
+ header->append ( XMP_Uns32 ( size - 24 ), '\0' );
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::WriteXMPObject ( LFA_FileRef fileRef, XMP_Uns32 len, const char* inBuffer )
+{
+ bool ret = false;
+
+ ASF_ObjectBase objectBase = { ASF_XMP_Metadata, 0 };
+ objectBase.size = MakeUns64LE ( len + kASF_ObjectBaseLen );
+
+ try {
+ LFA_Write ( fileRef, &objectBase, kASF_ObjectBaseLen );
+ LFA_Write ( fileRef, inBuffer, len );
+ ret = true;
+ } catch ( ... ) {}
+
+ return ret;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::UpdateXMPObject ( LFA_FileRef fileRef, const ObjectData& object, XMP_Uns32 len, const char * inBuffer )
+{
+ bool ret = false;
+
+ ASF_ObjectBase objectBase = { ASF_XMP_Metadata, 0 };
+ objectBase.size = MakeUns64LE ( len + kASF_ObjectBaseLen );
+
+ try {
+ LFA_Seek ( fileRef, object.pos, SEEK_SET );
+ LFA_Write ( fileRef, &objectBase, kASF_ObjectBaseLen );
+ LFA_Write ( fileRef, inBuffer, len );
+ ret = true;
+ } catch ( ... ) {}
+
+ return ret;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::CopyObject ( LFA_FileRef sourceRef, LFA_FileRef destRef, const ObjectData& object )
+{
+ try {
+ LFA_Seek ( sourceRef, object.pos, SEEK_SET );
+ LFA_Copy ( sourceRef, destRef, object.len );
+ } catch ( ... ) {
+ return false;
+ }
+
+ return true;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::ReadBuffer ( LFA_FileRef fileRef, XMP_Uns64 & pos, XMP_Uns64 len, char * outBuffer )
+{
+ try {
+
+ if ( (fileRef == 0) || (outBuffer == 0) ) return false;
+
+ LFA_Seek (fileRef, pos, SEEK_SET );
+ long bytesRead = LFA_Read ( fileRef, outBuffer, XMP_Int32(len), true );
+ if ( XMP_Uns32 ( bytesRead ) != len ) return false;
+
+ return true;
+
+ } catch ( ... ) {}
+
+ return false;
+
+}
+
+// =============================================================================================
+
+bool ASF_Support::WriteBuffer ( LFA_FileRef fileRef, XMP_Uns64 & pos, XMP_Uns32 len, const char * inBuffer )
+{
+ try {
+
+ if ( (fileRef == 0) || (inBuffer == 0) ) return false;
+
+ LFA_Seek (fileRef, pos, SEEK_SET );
+ LFA_Write( fileRef, inBuffer, len );
+
+ return true;
+
+ } catch ( ... ) {}
+
+ return false;
+
+}
+
+// =================================================================================================
+
+std::string ASF_Support::ReplaceString ( std::string& operand, std::string& str, int offset, int count )
+{
+ std::basic_string<char>::iterator iterF1, iterL1, iterF2, iterL2;
+
+ iterF1 = operand.begin() + offset;
+ iterL1 = operand.begin() + offset + count;
+ iterF2 = str.begin();
+ iterL2 = str.begin() + count;
+
+ return operand.replace ( iterF1, iterL1, iterF2, iterL2 );
+
+}
+
+// =================================================================================================
+
+ASF_LegacyManager::ASF_LegacyManager() : fields(fieldLast), broadcastSet(false), digestComputed(false),
+ imported(false), objectsExisting(0), objectsToExport(0), legacyDiff(0), padding(0)
+{
+ // Nothing more to do.
+}
+
+// =================================================================================================
+
+ASF_LegacyManager::~ASF_LegacyManager()
+{
+ // Nothing to do.
+}
+
+// =================================================================================================
+
+bool ASF_LegacyManager::SetField ( fieldType field, const std::string& value )
+{
+ if ( field >= fieldLast ) return false;
+
+ unsigned int maxSize = this->GetFieldMaxSize ( field );
+
+ if (value.size ( ) <= maxSize ) {
+ fields[field] = value;
+ } else {
+ fields[field] = value.substr ( 0, maxSize );
+ }
+
+ if ( field == fieldCopyrightURL ) NormalizeStringDisplayASCII ( fields[field] );
+
+ #if ! Exclude_LicenseURL_Recon
+ if ( field == fieldLicenseURL ) NormalizeStringDisplayASCII ( fields[field] );
+ #endif
+
+ return true;
+
+}
+
+// =================================================================================================
+
+std::string ASF_LegacyManager::GetField ( fieldType field )
+{
+ if ( field >= fieldLast ) return std::string();
+ return fields[field];
+}
+
+// =================================================================================================
+
+unsigned int ASF_LegacyManager::GetFieldMaxSize ( fieldType field )
+{
+ unsigned int maxSize = 0;
+
+ switch ( field ) {
+
+ case fieldCreationDate :
+ maxSize = 8;
+ break;
+
+ case fieldTitle :
+ case fieldAuthor :
+ case fieldCopyright :
+ case fieldDescription :
+ maxSize = 0xFFFF;
+ break;
+
+ case fieldCopyrightURL :
+#if ! Exclude_LicenseURL_Recon
+ case fieldLicenseURL :
+#endif
+ maxSize = 0xFFFFFFFF;
+ break;
+
+ default:
+ break;
+
+ }
+
+ return maxSize;
+
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::SetObjectExists ( objectType object )
+{
+ objectsExisting |= object;
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::SetBroadcast ( const bool broadcast )
+{
+ broadcastSet = broadcast;
+}
+
+// =================================================================================================
+
+bool ASF_LegacyManager::GetBroadcast()
+{
+ return broadcastSet;
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::ComputeDigest()
+{
+ MD5_CTX context;
+ MD5_Digest digest;
+ char buffer[40];
+
+ MD5Init ( &context );
+ digestStr.clear();
+ digestStr.reserve ( 160 );
+
+ for ( int type=0; type < fieldLast; ++type ) {
+
+ if (fields[type].size ( ) > 0 ) {
+ snprintf ( buffer, sizeof(buffer), "%d,", type );
+ digestStr.append ( buffer );
+ MD5Update ( &context, (XMP_Uns8*)fields[type].data(), fields[type].size() );
+ }
+
+ }
+
+ digestStr[digestStr.size()-1] = ';';
+
+ MD5Final ( digest, &context );
+
+ size_t in, out;
+ for ( in = 0, out = 0; in < 16; in += 1, out += 2 ) {
+ XMP_Uns8 byte = digest[in];
+ buffer[out] = ReconcileUtils::kHexDigits [ byte >> 4 ];
+ buffer[out+1] = ReconcileUtils::kHexDigits [ byte & 0xF ];
+ }
+ buffer[32] = 0;
+
+ digestStr.append ( buffer );
+
+ digestComputed = true;
+
+}
+
+// =================================================================================================
+
+bool ASF_LegacyManager::CheckDigest ( const SXMPMeta& xmp )
+{
+ bool ret = false;
+
+ if ( ! digestComputed ) this->ComputeDigest();
+
+ std::string oldDigest;
+
+ if ( xmp.GetProperty ( kXMP_NS_ASF, "NativeDigest", &oldDigest, 0 ) ) {
+ ret = (digestStr == oldDigest);
+ }
+
+ return ret;
+
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::SetDigest ( SXMPMeta* xmp )
+{
+ if ( ! digestComputed ) this->ComputeDigest();
+
+ xmp->SetProperty ( kXMP_NS_ASF, "NativeDigest", digestStr.c_str() );
+
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::ImportLegacy ( SXMPMeta* xmp )
+{
+ std::string utf8;
+
+ if ( ! broadcastSet ) {
+ ConvertMSDateToISODate ( fields[fieldCreationDate], &utf8 );
+ xmp->SetProperty ( kXMP_NS_XMP, "CreateDate", utf8.c_str(), kXMP_DeleteExisting );
+ }
+
+ FromUTF16 ( (UTF16Unit*)fields[fieldTitle].c_str(), (fields[fieldTitle].size() / 2), &utf8, false );
+ xmp->SetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", utf8.c_str(), kXMP_DeleteExisting );
+
+ xmp->DeleteProperty ( kXMP_NS_DC, "creator" );
+ FromUTF16 ( (UTF16Unit*)fields[fieldAuthor].c_str(), (fields[fieldAuthor].size() / 2), &utf8, false );
+ SXMPUtils::SeparateArrayItems ( xmp, kXMP_NS_DC, "creator", kXMPUtil_AllowCommas, utf8.c_str() );
+
+ FromUTF16 ( (UTF16Unit*)fields[fieldCopyright].c_str(), (fields[fieldCopyright].size() / 2), &utf8, false );
+ xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", utf8.c_str(), kXMP_DeleteExisting );
+
+ FromUTF16 ( (UTF16Unit*)fields[fieldDescription].c_str(), (fields[fieldDescription].size() / 2), &utf8, false );
+ xmp->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", utf8.c_str(), kXMP_DeleteExisting );
+
+ xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", fields[fieldCopyrightURL].c_str(), kXMP_DeleteExisting );
+
+#if ! Exclude_LicenseURL_Recon
+ xmp->SetProperty ( kXMP_NS_XMP_Rights, "Certificate", fields[fieldLicenseURL].c_str(), kXMP_DeleteExisting );
+#endif
+
+ imported = true;
+
+}
+
+// =================================================================================================
+
+int ASF_LegacyManager::ExportLegacy ( const SXMPMeta& xmp )
+{
+ int changed = 0;
+ objectsToExport = 0;
+ legacyDiff = 0;
+
+ std::string utf8;
+ std::string utf16;
+ XMP_OptionBits flags;
+
+ if ( ! broadcastSet ) {
+ if ( xmp.GetProperty ( kXMP_NS_XMP, "CreateDate", &utf8, &flags ) ) {
+ std::string date;
+ ConvertISODateToMSDate ( utf8, &date );
+ if ( fields[fieldCreationDate] != date ) {
+ legacyDiff += date.size();
+ legacyDiff -= fields[fieldCreationDate].size();
+ this->SetField ( fieldCreationDate, date );
+ objectsToExport |= objectFileProperties;
+ changed ++;
+ }
+ }
+ }
+
+ if ( xmp.GetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", 0, &utf8, &flags ) ) {
+ NormalizeStringTrailingNull ( utf8 );
+ ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false );
+ if ( fields[fieldTitle] != utf16 ) {
+ legacyDiff += utf16.size();
+ legacyDiff -= fields[fieldTitle].size();
+ this->SetField ( fieldTitle, utf16 );
+ objectsToExport |= objectContentDescription;
+ changed ++;
+ }
+ }
+
+ utf8.clear();
+ SXMPUtils::CatenateArrayItems ( xmp, kXMP_NS_DC, "creator", 0, 0, kXMPUtil_AllowCommas, &utf8 );
+ if ( ! utf8.empty() ) {
+ NormalizeStringTrailingNull ( utf8 );
+ ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false );
+ if ( fields[fieldAuthor] != utf16 ) {
+ legacyDiff += utf16.size();
+ legacyDiff -= fields[fieldAuthor].size();
+ this->SetField ( fieldAuthor, utf16 );
+ objectsToExport |= objectContentDescription;
+ changed ++;
+ }
+ }
+
+ if ( xmp.GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", 0, &utf8, &flags ) ) {
+ NormalizeStringTrailingNull ( utf8 );
+ ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false );
+ if ( fields[fieldCopyright] != utf16 ) {
+ legacyDiff += utf16.size();
+ legacyDiff -= fields[fieldCopyright].size();
+ this->SetField ( fieldCopyright, utf16 );
+ objectsToExport |= objectContentDescription;
+ changed ++;
+ }
+ }
+
+ if ( xmp.GetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", 0, &utf8, &flags ) ) {
+ NormalizeStringTrailingNull ( utf8 );
+ ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false );
+ if ( fields[fieldDescription] != utf16 ) {
+ legacyDiff += utf16.size();
+ legacyDiff -= fields[fieldDescription].size();
+ this->SetField ( fieldDescription, utf16 );
+ objectsToExport |= objectContentDescription;
+ changed ++;
+ }
+ }
+
+ if ( xmp.GetProperty ( kXMP_NS_XMP_Rights, "WebStatement", &utf8, &flags ) ) {
+ NormalizeStringTrailingNull ( utf8 );
+ if ( fields[fieldCopyrightURL] != utf8 ) {
+ legacyDiff += utf8.size();
+ legacyDiff -= fields[fieldCopyrightURL].size();
+ this->SetField ( fieldCopyrightURL, utf8 );
+ objectsToExport |= objectContentBranding;
+ changed ++;
+ }
+ }
+
+#if ! Exclude_LicenseURL_Recon
+ if ( xmp.GetProperty ( kXMP_NS_XMP_Rights, "Certificate", &utf8, &flags ) ) {
+ NormalizeStringTrailingNull ( utf8 );
+ if ( fields[fieldLicenseURL] != utf8 ) {
+ legacyDiff += utf8.size();
+ legacyDiff -= fields[fieldLicenseURL].size();
+ this->SetField ( fieldLicenseURL, utf8 );
+ objectsToExport |= objectContentEncryption;
+ changed ++;
+ }
+ }
+#endif
+
+ // find objects, that would need to be created on legacy export
+ int newObjects = (objectsToExport & !objectsExisting);
+
+ // calculate minimum storage for new objects, that might be created on export
+ if ( newObjects & objectContentDescription )
+ legacyDiff += sizeContentDescription;
+ if ( newObjects & objectContentBranding )
+ legacyDiff += sizeContentBranding;
+ if ( newObjects & objectContentEncryption )
+ legacyDiff += sizeContentEncryption;
+
+ ComputeDigest();
+
+ return changed;
+
+}
+
+// =================================================================================================
+
+bool ASF_LegacyManager::hasLegacyChanged()
+{
+ return (objectsToExport != 0);
+}
+
+// =================================================================================================
+
+XMP_Int64 ASF_LegacyManager::getLegacyDiff()
+{
+ return (this->hasLegacyChanged() ? legacyDiff : 0);
+}
+
+// =================================================================================================
+
+int ASF_LegacyManager::changedObjects()
+{
+ return objectsToExport;
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::SetPadding ( XMP_Int64 _padding )
+{
+ padding = _padding;
+}
+
+// =================================================================================================
+
+XMP_Int64 ASF_LegacyManager::GetPadding()
+{
+ return padding;
+}
+
+// =================================================================================================
+
+std::string ASF_LegacyManager::NormalizeStringDisplayASCII ( std::string& operand )
+{
+ std::basic_string<char>::iterator current = operand.begin();
+ std::basic_string<char>::iterator end = operand.end();;
+
+ for ( ; (current != end); ++current ) {
+ char element = *current;
+ if ( ( (element < 0x21) && (element != 0x00)) || (element > 0x7e) ) {
+ *current = '?';
+ }
+ }
+
+ return operand;
+
+}
+
+// =================================================================================================
+
+std::string ASF_LegacyManager::NormalizeStringTrailingNull ( std::string& operand )
+{
+ if ( ( operand.size() > 0) && (operand[operand.size() - 1] != '\0') ) {
+ operand.append ( 1, '\0' );
+ }
+
+ return operand;
+
+}
+
+// =================================================================================================
+
+int ASF_LegacyManager::DaysInMonth ( XMP_Int32 year, XMP_Int32 month )
+{
+
+ static short daysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
+
+ int days = daysInMonth [ month ];
+ if ( (month == 2) && IsLeapYear ( year ) ) days += 1;
+
+ return days;
+
+}
+
+// =================================================================================================
+
+bool ASF_LegacyManager::IsLeapYear ( long year )
+{
+
+ if ( year < 0 ) year = -year + 1; // Fold the negative years, assuming there is a year 0.
+
+ if ( (year % 4) != 0 ) return false; // Not a multiple of 4.
+ if ( (year % 100) != 0 ) return true; // A multiple of 4 but not a multiple of 100.
+ if ( (year % 400) == 0 ) return true; // A multiple of 400.
+
+ return false; // A multiple of 100 but not a multiple of 400.
+
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::ConvertMSDateToISODate ( std::string& source, std::string* dest )
+{
+
+ XMP_Int64 creationDate = GetUns64LE ( source.c_str() );
+ XMP_Int64 totalSecs = creationDate / (10*1000*1000);
+ XMP_Int32 nanoSec = ( ( XMP_Int32) (creationDate - (totalSecs * 10*1000*1000)) ) * 100;
+
+ XMP_Int32 days = (XMP_Int32) (totalSecs / 86400);
+ totalSecs -= ( ( XMP_Int64)days * 86400 );
+
+ XMP_Int32 hour = (XMP_Int32) (totalSecs / 3600);
+ totalSecs -= ( ( XMP_Int64)hour * 3600 );
+
+ XMP_Int32 minute = (XMP_Int32) (totalSecs / 60);
+ totalSecs -= ( ( XMP_Int64)minute * 60 );
+
+ XMP_Int32 second = (XMP_Int32)totalSecs;
+
+ // A little more simple code converts this to an XMP date string:
+
+ XMP_DateTime date;
+ memset ( &date, 0, sizeof ( date ) );
+
+ date.year = 1601; // The MS date origin.
+ date.month = 1;
+ date.day = 1;
+
+ date.day += days; // Add in the delta.
+ date.hour = hour;
+ date.minute = minute;
+ date.second = second;
+ date.nanoSecond = nanoSec;
+
+ SXMPUtils::ConvertToUTCTime ( &date ); // Normalize the date/time.
+ SXMPUtils::ConvertFromDate ( date, dest ); // Convert to an ISO 8601 string.
+
+}
+
+// =================================================================================================
+
+void ASF_LegacyManager::ConvertISODateToMSDate ( std::string& source, std::string* dest )
+{
+ XMP_DateTime date;
+ SXMPUtils::ConvertToDate ( source, &date );
+ SXMPUtils::ConvertToUTCTime ( &date );
+
+ XMP_Int64 creationDate;
+ creationDate = date.nanoSecond / 100;
+ creationDate += (XMP_Int64 ( date.second) * (10*1000*1000) );
+ creationDate += (XMP_Int64 ( date.minute) * 60 * (10*1000*1000) );
+ creationDate += (XMP_Int64 ( date.hour) * 3600 * (10*1000*1000) );
+
+ XMP_Int32 days = (date.day - 1);
+
+ --date.month;
+ while ( date.month >= 1 ) {
+ days += DaysInMonth ( date.year, date.month );
+ --date.month;
+ }
+
+ --date.year;
+ while ( date.year >= 1601 ) {
+ days += (IsLeapYear ( date.year) ? 366 : 365 );
+ --date.year;
+ }
+
+ creationDate += (XMP_Int64 ( days) * 86400 * (10*1000*1000) );
+
+ creationDate = GetUns64LE ( &creationDate );
+ dest->assign ( (const char*)&creationDate, 8 );
+
+}
diff --git a/source/XMPFiles/FormatSupport/ASF_Support.hpp b/source/XMPFiles/FormatSupport/ASF_Support.hpp
new file mode 100644
index 0000000..5260cb3
--- /dev/null
+++ b/source/XMPFiles/FormatSupport/ASF_Support.hpp
@@ -0,0 +1,224 @@
+#ifndef __ASF_Support_hpp__
+#define __ASF_Support_hpp__ 1
+
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "XMP_Environment.h" // ! This must be the first include.
+
+#include "XMPFiles_Impl.hpp"
+#include "Reconcile_Impl.hpp"
+
+// currently exclude LicenseURL from reconciliation
+#define Exclude_LicenseURL_Recon 1
+#define EXCLUDE_LICENSEURL_RECON 1
+
+// Defines for platforms other than Win
+#if ! XMP_WinBuild
+
+ typedef struct _GUID
+ {
+ XMP_Uns32 Data1;
+ XMP_Uns16 Data2;
+ XMP_Uns16 Data3;
+ XMP_Uns8 Data4[8];
+ } GUID;
+
+ int IsEqualGUID ( const GUID& guid1, const GUID& guid2 );
+
+ static const GUID GUID_NULL = { 0x0, 0x0, 0x0, { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } };
+
+#endif
+
+// header object
+static const GUID ASF_Header_Object = { MakeUns32LE(0x75b22630), MakeUns16LE(0x668e), MakeUns16LE(0x11cf), { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c } };
+// contains ...
+static const GUID ASF_File_Properties_Object = { MakeUns32LE(0x8cabdca1), MakeUns16LE(0xa947), MakeUns16LE(0x11cf), { 0x8e, 0xe4, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 } };
+static const GUID ASF_Content_Description_Object = { MakeUns32LE(0x75b22633), MakeUns16LE(0x668e), MakeUns16LE(0x11cf), { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c } };
+static const GUID ASF_Content_Branding_Object = { MakeUns32LE(0x2211b3fa), MakeUns16LE(0xbd23), MakeUns16LE(0x11d2), { 0xb4, 0xb7, 0x00, 0xa0, 0xc9, 0x55, 0xfc, 0x6e } };
+static const GUID ASF_Content_Encryption_Object = { MakeUns32LE(0x2211b3fb), MakeUns16LE(0xbd23), MakeUns16LE(0x11d2), { 0xb4, 0xb7, 0x00, 0xa0, 0xc9, 0x55, 0xfc, 0x6e } };
+// padding
+// Remark: regarding to Microsofts spec only the ASF_Header_Object contains a ASF_Padding_Object
+// Real world files show, that the ASF_Header_Extension_Object contains a ASF_Padding_Object
+static const GUID ASF_Header_Extension_Object = { MakeUns32LE(0x5fbf03b5), MakeUns16LE(0xa92e), MakeUns16LE(0x11cf), { 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 } };
+static const GUID ASF_Padding_Object = { MakeUns32LE(0x1806d474), MakeUns16LE(0xcadf), MakeUns16LE(0x4509), { 0xa4, 0xba, 0x9a, 0xab, 0xcb, 0x96, 0xaa, 0xe8 } };
+
+// data object
+static const GUID ASF_Data_Object = { MakeUns32LE(0x75b22636), MakeUns16LE(0x668e), MakeUns16LE(0x11cf), { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c } };
+
+// XMP object
+static const GUID ASF_XMP_Metadata = { MakeUns32LE(0xbe7acfcb), MakeUns16LE(0x97a9), MakeUns16LE(0x42e8), { 0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac } };
+
+static const int guidLen = sizeof(GUID);
+
+typedef struct _ASF_ObjectBase
+{
+ GUID guid;
+ XMP_Uns64 size;
+
+} ASF_ObjectBase;
+
+static const int kASF_ObjectBaseLen = sizeof(ASF_ObjectBase);
+
+// =================================================================================================
+
+class ASF_LegacyManager {
+public:
+
+ enum objectType {
+ objectFileProperties = 1 << 0,
+ objectContentDescription = 1 << 1,
+ objectContentBranding = 1 << 2,
+ objectContentEncryption = 1 << 3
+ };
+
+ enum minObjectSize {
+ sizeContentDescription = 34,
+ sizeContentBranding = 40,
+ sizeContentEncryption = 40
+ };
+
+ enum fieldType {
+ // File_Properties_Object
+ fieldCreationDate = 0,
+ // Content_Description_Object
+ fieldTitle,
+ fieldAuthor,
+ fieldCopyright,
+ fieldDescription,
+ // Content_Branding_Object
+ fieldCopyrightURL,
+ #if ! Exclude_LicenseURL_Recon
+ // Content_Encryption_Object
+ fieldLicenseURL,
+ #endif
+ // last
+ fieldLast
+ };
+
+ ASF_LegacyManager();
+ virtual ~ASF_LegacyManager();
+
+ bool SetField ( fieldType field, const std::string& value );
+ std::string GetField ( fieldType field );
+ unsigned int GetFieldMaxSize ( fieldType field );
+
+ void SetObjectExists ( objectType object );
+
+ void SetBroadcast ( const bool broadcast );
+ bool GetBroadcast();
+
+ void ComputeDigest();
+ bool CheckDigest ( const SXMPMeta& xmp );
+ void SetDigest ( SXMPMeta* xmp );
+
+ void ImportLegacy ( SXMPMeta* xmp );
+ int ExportLegacy ( const SXMPMeta& xmp );
+ bool hasLegacyChanged();
+ XMP_Int64 getLegacyDiff();
+ int changedObjects();
+
+ void SetPadding ( XMP_Int64 padding );
+ XMP_Int64 GetPadding();
+
+private:
+
+ typedef std::vector<std::string> TFields;
+ TFields fields;
+ bool broadcastSet;
+
+ std::string digestStr;
+ bool digestComputed;
+
+ bool imported;
+ int objectsExisting;
+ int objectsToExport;
+ XMP_Int64 legacyDiff;
+ XMP_Int64 padding;
+
+ static std::string NormalizeStringDisplayASCII ( std::string& operand );
+ static std::string NormalizeStringTrailingNull ( std::string& operand );
+
+ static void ConvertMSDateToISODate ( std::string& source, std::string* dest );
+ static void ConvertISODateToMSDate ( std::string& source, std::string* dest );
+
+ static int DaysInMonth ( XMP_Int32 year, XMP_Int32 month );
+ static bool IsLeapYear ( long year );
+
+}; // class ASF_LegacyManager
+
+// =================================================================================================
+
+class ASF_Support {
+public:
+
+ class ObjectData {
+ public:
+ ObjectData() : pos(0), len(0), guid(GUID_NULL), xmp(false) {}
+ virtual ~ObjectData() {}
+ XMP_Uns64 pos; // file offset of object
+ XMP_Uns64 len; // length of object data
+ GUID guid; // object GUID
+ bool xmp; // object with XMP ?
+ };
+
+ typedef std::vector<ObjectData> ObjectVector;
+ typedef ObjectVector::iterator ObjectIterator;
+
+ class ObjectState {
+
+ public:
+ ObjectState() : xmpPos(0), xmpLen(0), xmpIsLastObject(false), broadcast(false) {}
+ virtual ~ObjectState() {}
+ XMP_Uns64 xmpPos;
+ XMP_Uns64 xmpLen;
+ bool xmpIsLastObject;
+ bool broadcast;
+ ObjectData xmpObject;
+ ObjectVector objects;
+ };
+
+ ASF_Support();
+ ASF_Support ( ASF_LegacyManager* legacyManager );
+ virtual ~ASF_Support();
+
+ long OpenASF ( LFA_FileRef fileRef, ObjectState & inOutObjectState );
+
+ bool ReadObject ( LFA_FileRef fileRef, ObjectState & inOutObjectState, XMP_Uns64 * objectLength, XMP_Uns64 & inOutPosition );
+
+ bool ReadHeaderObject ( LFA_FileRef fileRef, ObjectState& inOutObjectState, const ObjectData& newObject );
+ bool WriteHeaderObject ( LFA_FileRef sourceRef, LFA_FileRef destRef, const ObjectData& object, ASF_LegacyManager& legacyManager, bool usePadding );
+ bool UpdateHeaderObject ( LFA_FileRef fileRef, const ObjectData& object, ASF_LegacyManager& legacyManager );
+
+ bool UpdateFileSize ( LFA_FileRef fileRef );
+
+ bool ReadHeaderExtensionObject ( LFA_FileRef fileRef, ObjectState& inOutObjectState, const XMP_Uns64& pos, const ASF_ObjectBase& objectBase );
+ static bool WriteHeaderExtensionObject ( const std::string& buffer, std::string* header, const ASF_ObjectBase& objectBase, const int reservePadding );
+
+ static bool CreatePaddingObject ( std::string* header, const XMP_Uns64 size );
+
+ static bool WriteXMPObject ( LFA_FileRef fileRef, XMP_Uns32 len, const char* inBuffer );
+ static bool UpdateXMPObject ( LFA_FileRef fileRef, const ObjectData& object, XMP_Uns32 len, const char * inBuffer );
+ static bool CopyObject ( LFA_FileRef sourceRef, LFA_FileRef destRef, const ObjectData& object );
+
+ static bool ReadBuffer ( LFA_FileRef fileRef, XMP_Uns64 & pos, XMP_Uns64 len, char * outBuffer );
+ static bool WriteBuffer ( LFA_FileRef fileRef, XMP_Uns64 & pos, XMP_Uns32 len, const char * inBuffer );
+
+private:
+
+ ASF_LegacyManager* legacyManager;
+ XMP_Uns64 posFileSizeInfo;
+
+ static std::string ReplaceString ( std::string& operand, std::string& str, int offset, int count );
+
+}; // class ASF_Support
+
+// =================================================================================================
+
+#endif // __ASF_Support_hpp__
diff --git a/source/XMPFiles/FormatSupport/ID3_Support.cpp b/source/XMPFiles/FormatSupport/ID3_Support.cpp
index 793f925..ee8c008 100644
--- a/source/XMPFiles/FormatSupport/ID3_Support.cpp
+++ b/source/XMPFiles/FormatSupport/ID3_Support.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -8,6 +8,8 @@
// =================================================================================================
#include "XMP_Environment.h" // ! This must be the first include.
+#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed.
+
#include "XMP_Const.h"
#include "ID3_Support.hpp"
@@ -15,6 +17,8 @@
#include "UnicodeConversions.hpp"
#include "Reconcile_Impl.hpp"
+#include <stdio.h>
+
#if XMP_WinBuild
#pragma warning ( disable : 4996 ) // '...' was declared deprecated
#endif
@@ -164,7 +168,7 @@ namespace ID3_Support {
static bool GetFrameInfo(LFA_FileRef inFileRef, XMP_Uns8 bVersion, char *strFrameID, XMP_Uns8 &cflag1, XMP_Uns8 &cflag2, unsigned long &dwSize);
static bool ReadSize(LFA_FileRef inFileRef, XMP_Uns8 bVersion, unsigned long &dwSize);
static unsigned long CalculateSize(XMP_Uns8 bVersion, unsigned long dwSizeIn);
- static bool LoadTagHeaderAndUnknownFrames(LFA_FileRef inFileRef, char *strBuffer, bool fRecon, unsigned long &posPad);
+ static bool LoadTagHeaderAndUnknownFrames(LFA_FileRef inFileRef, char *strBuffer, size_t strBufferLen, bool fRecon, unsigned long &posPad);
#define GetFilePosition(file) LFA_Seek ( file, 0, SEEK_CUR )
@@ -293,6 +297,8 @@ bool FindFrame ( LFA_FileRef inFileRef, char* strFrame, XMP_Int64 & posFrame, un
while ( posCur < posEnd ) {
+ if ( (posEnd - posCur) < k_dwTagHeaderSize ) break; // Not enough room for a header, must be padding.
+
char szFrameID[5] = {"xxxx"};
unsigned long dwFrameSize = 0;
XMP_Uns8 cflag1 = 0, cflag2 = 0;
@@ -380,7 +386,7 @@ bool GetFrameData ( LFA_FileRef inFileRef, char* strFrame, char* buffer, unsigne
unsigned long dwOffset = 3; // Skip the 3 byte language code.
if ( (bEncoding == 0) || (bEncoding == 3) ) {
- dwOffset += (strlen ( &strData[3] ) + 1); // Skip the descriptor and nul.
+ dwOffset += (unsigned long)(strlen ( &strData[3] ) + 1); // Skip the descriptor and nul.
} else {
UTF16Unit* u16Ptr = (UTF16Unit*) (&strData[3]);
for ( ; *u16Ptr != 0; ++u16Ptr ) dwOffset += 2; // Skip the descriptor.
@@ -518,7 +524,7 @@ bool AddXMPTagToID3Buffer ( char * strCur, unsigned long * pdwCurOffset, unsigne
snprintf ( strGenre, sizeof(strGenre), "(%d)", iFound ); // AUDIT: Using sizeof(strGenre) is safe.
strXMPTag = strGenre;
- dwXMPLength = strlen(strXMPTag);
+ dwXMPLength = (long)strlen(strXMPTag);
}
@@ -535,13 +541,13 @@ bool AddXMPTagToID3Buffer ( char * strCur, unsigned long * pdwCurOffset, unsigne
bEncoding = 1; // Will convert to UTF-16 later.
} else {
strXMPTag = tempLatin1.c_str(); // Use the Latin-1 encoding for output.
- dwXMPLength = tempLatin1.size();
+ dwXMPLength = (long)tempLatin1.size();
}
std::string strUTF16;
if ( bEncoding == 1 ) {
ToUTF16 ( (UTF8Unit*)strXMPTag, dwXMPLength, &strUTF16, false /* little endian */ );
- dwXMPLength = strUTF16.size() + 2; // ! Include the (to be inserted) BOM in the count.
+ dwXMPLength = (long)strUTF16.size() + 2; // ! Include the (to be inserted) BOM in the count.
}
// Frame Structure
@@ -737,7 +743,7 @@ bool SetMetaData ( LFA_FileRef inFileRef, char* strXMPPacket, unsigned long dwXM
return false;
}
- LoadTagHeaderAndUnknownFrames ( inFileRef, szID3Buffer, fRecon, id3BufferLen );
+ LoadTagHeaderAndUnknownFrames ( inFileRef, szID3Buffer, sizeof(szID3Buffer), fRecon, id3BufferLen );
unsigned long spareLen = (k_dwFrameHeaderSize + dwOldID3ContentSize) - id3BufferLen;
@@ -812,7 +818,7 @@ bool SetMetaData ( LFA_FileRef inFileRef, char* strXMPPacket, unsigned long dwXM
// =================================================================================================
-bool LoadTagHeaderAndUnknownFrames ( LFA_FileRef inFileRef, char * strBuffer, bool fRecon, unsigned long & posPad )
+bool LoadTagHeaderAndUnknownFrames ( LFA_FileRef inFileRef, char * strBuffer, size_t strBufferLen, bool fRecon, unsigned long & posPad )
{
LFA_Seek ( inFileRef, 3ULL, SEEK_SET ); // Point after the "ID3"
@@ -826,6 +832,7 @@ bool LoadTagHeaderAndUnknownFrames ( LFA_FileRef inFileRef, char * strBuffer, bo
unsigned long dwExtendedTag = SkipExtendedHeader ( inFileRef, v1, flags );
LFA_Seek ( inFileRef, 0ULL, SEEK_SET );
+ XMP_Assert ( strBufferLen >= k_dwTagHeaderSize );
LFA_Read ( inFileRef, strBuffer, k_dwTagHeaderSize );
dwOffset += k_dwTagHeaderSize;
@@ -841,6 +848,9 @@ bool LoadTagHeaderAndUnknownFrames ( LFA_FileRef inFileRef, char * strBuffer, bo
XMP_Int64 posEnd = posCur + dwTagSize;
while ( posCur < posEnd ) {
+
+ XMP_Assert ( k_dwTagHeaderSize == 10 );
+ if ( (posEnd - posCur) < k_dwTagHeaderSize ) break; // Not enough room for a header, must be padding.
char szFrameID[5] = {"xxxx"};
unsigned long dwFrameSize = 0;
@@ -890,7 +900,10 @@ bool LoadTagHeaderAndUnknownFrames ( LFA_FileRef inFileRef, char * strBuffer, bo
} else {
// Unknown frame, let's copy it
LFA_Seek ( inFileRef, -(long)k_dwFrameHeaderSize, SEEK_CUR );
- LFA_Read ( inFileRef, strBuffer+dwOffset, dwFrameSize+k_dwFrameHeaderSize );
+ if ( (dwOffset > strBufferLen) || ((dwFrameSize + k_dwFrameHeaderSize) > (strBufferLen - dwOffset)) ) {
+ XMP_Throw ( "Avoiding I/O buffer overflow", kXMPErr_InternalFailure );
+ }
+ LFA_Read ( inFileRef, (strBuffer + dwOffset), (dwFrameSize + k_dwFrameHeaderSize) );
dwOffset += dwFrameSize+k_dwFrameHeaderSize;
}
@@ -984,6 +997,8 @@ static bool FindXMPFrame ( LFA_FileRef inFileRef, XMP_Int64 & posXMP, XMP_Int64
while ( posCur < posEnd ) {
+ if ( (posEnd - posCur) < k_dwTagHeaderSize ) break; // Not enough room for a header, must be padding.
+
char szFrameID[5] = {"xxxx"};
unsigned long dwFrameSize = 0;
XMP_Uns8 cflag1 = 0, cflag2 = 0;
@@ -1135,3 +1150,7 @@ static unsigned long CalculateSize ( XMP_Uns8 bVersion, unsigned long dwSizeIn )
}
} // namespace ID3_Support
+
+// =================================================================================================
+
+#endif // XMP_UNIXBuild
diff --git a/source/XMPFiles/FormatSupport/ID3_Support.hpp b/source/XMPFiles/FormatSupport/ID3_Support.hpp
index 17970cc..975ccf4 100644
--- a/source/XMPFiles/FormatSupport/ID3_Support.hpp
+++ b/source/XMPFiles/FormatSupport/ID3_Support.hpp
@@ -11,6 +11,7 @@
// =================================================================================================
#include "XMP_Environment.h" // ! This must be the first include.
+#if ! XMP_UNIXBuild // Closes at very bottom. Disabled on UNIX until legacy-as-local is fixed.
#include <vector>
@@ -36,4 +37,5 @@ namespace ID3_Support
} // namespace ID3_Support
+#endif // XMP_UNIXBuild
#endif // __ID3_Support_hpp__
diff --git a/source/XMPFiles/FormatSupport/IPTC_Support.cpp b/source/XMPFiles/FormatSupport/IPTC_Support.cpp
index e6e80ef..48e0309 100644
--- a/source/XMPFiles/FormatSupport/IPTC_Support.cpp
+++ b/source/XMPFiles/FormatSupport/IPTC_Support.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2006-2007 Adobe Systems Incorporated
+// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -226,11 +226,11 @@ void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, boo
if ( recNum == 2 ) {
if ( ! foundRec2 ) {
foundRec2 = true;
- this->rec2Offset = dsPtr - this->iptcContent;
+ this->rec2Offset = (XMP_Uns32) (dsPtr - this->iptcContent);
this->rec2Length = this->iptcLength - this->rec2Offset; // ! In case there are no other records.
}
} else {
- this->rec2Length = dsPtr - (this->iptcContent + this->rec2Offset);
+ this->rec2Length = (XMP_Uns32) (dsPtr - (this->iptcContent + this->rec2Offset));
break; // The records are in ascending order, done.
}
@@ -298,7 +298,13 @@ size_t IPTC_Manager::GetDataSet_UTF8 ( XMP_Uns8 id, std::string * utf8Str, size_
if ( this->utf8Encoding ) {
utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen );
} else {
- ReconcileUtils::LocalToUTF8 ( dsInfo.dataPtr, dsInfo.dataLen, utf8Str );
+ #if ! XMP_UNIXBuild
+ ReconcileUtils::LocalToUTF8 ( dsInfo.dataPtr, dsInfo.dataLen, utf8Str );
+ #else
+ // ! Hack until legacy-as-local issues are resolved for generic UNIX.
+ if ( ! ReconcileUtils::IsUTF8 ( dsInfo.dataPtr, dsInfo.dataLen ) ) return 0;
+ utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen );
+ #endif
}
}
@@ -333,6 +339,8 @@ void IPTC_Manager::DisposeLooseValue ( DataSetInfo & dsInfo )
// =================================================================================================
// =================================================================================================
+#if ! XMP_UNIXBuild // ! Disable IPTC output for generic UNIX until the legacy-as-local issues are resolved.
+
// =================================================================================================
// IPTC_Writer::~IPTC_Writer
// =========================
@@ -381,7 +389,7 @@ void IPTC_Writer::SetDataSet_UTF8 ( XMP_Uns8 id, const void* utf8Ptr, XMP_Uns32
// It round-tripped without loss, keep local encoding.
tempPtr = (XMP_Uns8*) localStr.data();
- dataLen = localStr.size();
+ dataLen = (XMP_Uns32)localStr.size();
// } else {
@@ -401,7 +409,7 @@ void IPTC_Writer::SetDataSet_UTF8 ( XMP_Uns8 id, const void* utf8Ptr, XMP_Uns32
// Back up to just before a byte with 11 in the high order 2 bits.
if ( dataLen > knownDS->maxLen ) {
- dataLen = knownDS->maxLen;
+ dataLen = (XMP_Uns32)knownDS->maxLen;
if ( this->utf8Encoding && ((tempPtr[dataLen] >> 6) == 2) ) {
for ( ; (dataLen > 0) && ((tempPtr[dataLen] >> 6) != 3); --dataLen ) {}
}
@@ -591,7 +599,7 @@ XMP_Uns32 IPTC_Writer::UpdateMemoryDataSets ( void** dataPtr )
this->ParseMemoryDataSets ( newContent, newLength, false ); // Don't make another copy of the content.
XMP_Assert ( this->iptcLength == newLength );
- this->ownedContent = true; // We really do own the new content.
+ this->ownedContent = (newLength > 0); // We really do own the new content, if not empty.
// Done.
@@ -700,4 +708,8 @@ bool IPTC_Writer::CheckRoundTripLoss()
} // IPTC_Writer::CheckRoundTripLoss
-#endif
+#endif // Round-trip loss checking
+
+// =================================================================================================
+
+#endif // ! XMP_UNIXBuild
diff --git a/source/XMPFiles/FormatSupport/IPTC_Support.hpp b/source/XMPFiles/FormatSupport/IPTC_Support.hpp
index 11ff28e..25ac9c2 100644
--- a/source/XMPFiles/FormatSupport/IPTC_Support.hpp
+++ b/source/XMPFiles/FormatSupport/IPTC_Support.hpp
@@ -269,6 +269,8 @@ private:
// IPTC_Writer
// ===========
+#if ! XMP_UNIXBuild // ! Disable IPTC output for generic UNIX until the legacy-as-local issues are resolved.
+
class IPTC_Writer : public IPTC_Manager {
public:
@@ -297,6 +299,8 @@ private:
}; // IPTC_Writer
+#endif // ! XMP_UNIXBuild
+
// =================================================================================================
#endif // __IPTC_Support_hpp__
diff --git a/source/XMPFiles/FormatSupport/PNG_Support.cpp b/source/XMPFiles/FormatSupport/PNG_Support.cpp
index ed55dc6..f074729 100644
--- a/source/XMPFiles/FormatSupport/PNG_Support.cpp
+++ b/source/XMPFiles/FormatSupport/PNG_Support.cpp
@@ -6,8 +6,8 @@
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
-
#include "PNG_Support.hpp"
+#include <string.h>
typedef std::basic_string<unsigned char> filebuffer;
@@ -114,7 +114,7 @@ namespace PNG_Support
// read first and following chunks
while ( ReadChunk ( fileRef, inOutChunkState, &name, &len, pos) ) {}
- return inOutChunkState.chunks.size();
+ return (long)inOutChunkState.chunks.size();
}
diff --git a/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp b/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp
index c1acfdf..f8e6290 100644
--- a/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp
+++ b/source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2006-2007 Adobe Systems Incorporated
+// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -10,6 +10,8 @@
#include "PSIR_Support.hpp"
#include "EndianUtils.hpp"
+#include <string.h>
+
// =================================================================================================
/// \file PSIR_FileWriter.cpp
/// \brief Implementation of the file-based or read-write form of PSIR_Manager.
@@ -64,7 +66,8 @@ void PSIR_FileWriter::DeleteExistingInfo()
this->memContent = 0;
this->memLength = 0;
- this->changed = false;
+ this->changed = false;
+ this->legacyDeleted = false;
this->memParsed = false;
this->fileParsed = false;
this->ownedContent = false;
@@ -74,10 +77,6 @@ void PSIR_FileWriter::DeleteExistingInfo()
// =================================================================================================
// PSIR_FileWriter::~PSIR_FileWriter
// =================================
-//
-// The InternalRsrcInfo destructor will deallocate the data for changed image resources. It does not
-// know whether they are memory-parsed or file-parsed though, so it won't deallocate captured but
-// unchanged file-parsed resources. Mark those as changed to make the destructor deallocate them.
PSIR_FileWriter::~PSIR_FileWriter()
{
@@ -87,15 +86,6 @@ PSIR_FileWriter::~PSIR_FileWriter()
XMP_Assert ( this->memContent != 0 );
free ( this->memContent );
}
-
- if ( this->fileParsed ) {
- InternalRsrcMap::iterator irPos = this->imgRsrcs.begin();
- InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
- for ( ; irPos != irEnd; ++irPos ) {
- if ( irPos->second.dataPtr != 0 ) irPos->second.changed = true;
- }
- }
-
} // PSIR_FileWriter::~PSIR_FileWriter
@@ -127,35 +117,35 @@ bool PSIR_FileWriter::GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const
void PSIR_FileWriter::SetImgRsrc ( XMP_Uns16 id, const void* clientPtr, XMP_Uns32 length )
{
+ InternalRsrcInfo* rsrcPtr = 0;
InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id );
-
- if ( (rsrcPos != this->imgRsrcs.end()) &&
- (length == rsrcPos->second.dataLen) &&
- (memcmp ( rsrcPos->second.dataPtr, clientPtr, length ) == 0) ) {
- return; // ! The value is unchanged, exit.
- }
-
- void* dataPtr = malloc ( length );
- if ( dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
- memcpy ( dataPtr, clientPtr, length ); // AUDIT: Safe, malloc'ed length bytes above.
if ( rsrcPos == this->imgRsrcs.end() ) {
// This resource is not yet in the map, create the map entry.
- InternalRsrcInfo newRsrc ( id, length, dataPtr, (XMP_Uns32)(-1) );
- newRsrc.changed = true;
- this->imgRsrcs[id] = newRsrc;
-
- } else {
-
- // This resource is in the map, update the existing map entry.
- InternalRsrcInfo* rsrcInfo = &rsrcPos->second;
- if ( rsrcInfo->changed && (rsrcInfo->dataPtr != 0) ) free ( rsrcInfo->dataPtr );
- rsrcInfo->dataPtr = dataPtr;
- rsrcInfo->dataLen = length;
- rsrcInfo->changed = true;
+ InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, length, this->fileParsed ) );
+ rsrcPos = this->imgRsrcs.insert ( rsrcPos, mapValue );
+ rsrcPtr = &rsrcPos->second;
+ } else {
+
+ rsrcPtr = &rsrcPos->second;
+
+ // The resource already exists, make sure the value is actually changing.
+ if ( (length == rsrcPtr->dataLen) &&
+ (memcmp ( rsrcPtr->dataPtr, clientPtr, length ) == 0) ) {
+ return;
+ }
+
+ rsrcPtr->FreeData(); // Release any existing data allocation.
+ rsrcPtr->dataLen = length; // And this might be changing.
+
}
+
+ rsrcPtr->changed = true;
+ rsrcPtr->dataPtr = malloc ( length );
+ if ( rsrcPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+ memcpy ( rsrcPtr->dataPtr, clientPtr, length ); // AUDIT: Safe, malloc'ed length bytes above.
this->changed = true;
@@ -172,6 +162,7 @@ void PSIR_FileWriter::DeleteImgRsrc ( XMP_Uns16 id )
this->imgRsrcs.erase ( id );
this->changed = true;
+ if ( id != kPSIR_XMP ) this->legacyDeleted = true;
} // PSIR_FileWriter::DeleteImgRsrc
@@ -183,6 +174,7 @@ bool PSIR_FileWriter::IsLegacyChanged()
{
if ( ! this->changed ) return false;
+ if ( this->legacyDeleted ) return true;
InternalRsrcMap::iterator irPos = this->imgRsrcs.begin();
InternalRsrcMap::iterator irEnd = this->imgRsrcs.end();
@@ -242,18 +234,21 @@ void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length,
XMP_Uns32 dataLen = GetUns32BE(psirPtr);
psirPtr += 4; // Advance to the resource data.
- XMP_Uns32 dataOffset = psirPtr - this->memContent;
+ XMP_Uns32 dataOffset = (XMP_Uns32) ( psirPtr - this->memContent );
XMP_Uns8* nextRsrc = psirPtr + ((dataLen + 1) & 0xFFFFFFFEUL); // ! Round up to an even offset.
if ( (dataLen > length) || (psirPtr > psirEnd-dataLen) ) break; // Bad image resource. Throw instead?
if ( type == k8BIM ) {
- InternalRsrcInfo newRsrc ( id, dataLen, psirPtr, dataOffset );
- this->imgRsrcs[id] = newRsrc;
- if ( nameLen != 0 ) this->imgRsrcs[id].rsrcName = namePtr;
+ InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, dataLen, kIsMemoryBased ) );
+ InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.insert ( this->imgRsrcs.end(), mapValue );
+ InternalRsrcInfo* rsrcPtr = &rsrcPos->second;
+ rsrcPtr->dataPtr = psirPtr;
+ rsrcPtr->origOffset = dataOffset;
+ if ( nameLen != 0 ) rsrcPtr->rsrcName = namePtr;
} else {
- XMP_Uns32 rsrcOffset = origin - this->memContent;
- XMP_Uns32 rsrcLength = nextRsrc - origin; // Includes trailing pad.
+ XMP_Uns32 rsrcOffset = XMP_Uns32( origin - this->memContent );
+ XMP_Uns32 rsrcLength = XMP_Uns32( nextRsrc - origin ); // Includes trailing pad.
XMP_Assert ( (rsrcLength & 1) == 0 );
this->otherRsrcs.push_back ( OtherRsrcInfo ( rsrcOffset, rsrcLength ) );
}
@@ -282,9 +277,10 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length
ioBuf.filePos = LFA_Seek ( fileRef, 0, SEEK_CUR );
XMP_Int64 psirOrigin = ioBuf.filePos; // Need this to determine the resource data offsets.
-
XMP_Int64 fileEnd = ioBuf.filePos + length;
+ std::string rsrcPName;
+
while ( (ioBuf.filePos + (ioBuf.ptr - ioBuf.data)) < fileEnd ) {
ok = CheckFileSpace ( fileRef, &ioBuf, 12 ); // The minimal image resource takes 12 bytes.
@@ -296,12 +292,14 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length
XMP_Uns16 id = GetUns16BE(ioBuf.ptr+4);
ioBuf.ptr += 6; // Advance to the resource name.
- XMP_Uns16 nameLen = ioBuf.ptr[0]; // ! The length for the Pascal string, w/ room for "+2".
- nameLen = (nameLen + 2) & 0xFFFE; // ! Round up to an even total. Yes, +2!
- ok = CheckFileSpace ( fileRef, &ioBuf, nameLen+4 ); // Get the name and data length.
+ XMP_Uns16 nameLen = ioBuf.ptr[0]; // ! The length for the Pascal string.
+ XMP_Uns16 paddedLen = (nameLen + 2) & 0xFFFE; // ! Round up to an even total. Yes, +2!
+ ok = CheckFileSpace ( fileRef, &ioBuf, paddedLen+4 ); // Get the name text and the data length.
if ( ! ok ) break; // Bad image resource. Throw instead?
+
+ if ( nameLen > 0 ) rsrcPName.assign ( (char*)(ioBuf.ptr), paddedLen ); // ! Include the length byte and pad.
- ioBuf.ptr += nameLen; // Move to the data length.
+ ioBuf.ptr += paddedLen; // Move to the data length.
XMP_Uns32 dataLen = GetUns32BE(ioBuf.ptr);
XMP_Uns32 dataTotal = ((dataLen + 1) & 0xFFFFFFFEUL); // Round up to an even total.
ioBuf.ptr += 4; // Advance to the resource data.
@@ -316,39 +314,37 @@ void PSIR_FileWriter::ParseFileResources ( LFA_FileRef fileRef, XMP_Uns32 length
continue;
}
- InternalRsrcInfo newRsrc ( id, dataLen, 0, (XMP_Uns32)thisDataPos );
- this->imgRsrcs[id] = newRsrc;
+ InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, dataLen, kIsFileBased ) );
+ InternalRsrcMap::iterator newRsrc = this->imgRsrcs.insert ( this->imgRsrcs.end(), mapValue );
+ InternalRsrcInfo* rsrcPtr = &newRsrc->second;
+
+ rsrcPtr->origOffset = (XMP_Uns32)thisDataPos;
+
+ if ( nameLen > 0 ) {
+ rsrcPtr->rsrcName = (XMP_Uns8*) malloc ( paddedLen );
+ if ( rsrcPtr->rsrcName == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+ memcpy ( (void*)rsrcPtr->rsrcName, rsrcPName.c_str(), paddedLen ); // AUDIT: Safe, allocated enough bytes above.
+ }
if ( ! IsMetadataImgRsrc ( id ) ) {
MoveToOffset ( fileRef, nextRsrcPos, &ioBuf );
continue;
}
- newRsrc.dataPtr = malloc ( dataLen );
- if ( newRsrc.dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
-
- try {
-
- if ( dataTotal <= kIOBufferSize ) {
- // The image resource data fits within the I/O buffer.
- ok = CheckFileSpace ( fileRef, &ioBuf, dataTotal );
- if ( ! ok ) break; // Bad image resource. Throw instead?
- memcpy ( (void*)newRsrc.dataPtr, ioBuf.ptr, dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above.
- ioBuf.ptr += dataTotal; // ! Add the rounded length.
- } else {
- // The image resource data is bigger than the I/O buffer.
- LFA_Seek ( fileRef, (ioBuf.filePos + (ioBuf.ptr - ioBuf.data)), SEEK_SET );
- LFA_Read ( fileRef, (void*)newRsrc.dataPtr, dataLen );
- FillBuffer ( fileRef, nextRsrcPos, &ioBuf );
- }
-
- this->imgRsrcs[id].dataPtr = newRsrc.dataPtr;
-
- } catch ( ... ) {
-
- free ( (void*)newRsrc.dataPtr );
- throw;
+ rsrcPtr->dataPtr = malloc ( dataLen ); // ! Allocate after the IsMetadataImgRsrc check.
+ if ( rsrcPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
+ if ( dataTotal <= kIOBufferSize ) {
+ // The image resource data fits within the I/O buffer.
+ ok = CheckFileSpace ( fileRef, &ioBuf, dataTotal );
+ if ( ! ok ) break; // Bad image resource. Throw instead?
+ memcpy ( (void*)rsrcPtr->dataPtr, ioBuf.ptr, dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above.
+ ioBuf.ptr += dataTotal; // ! Add the rounded length.
+ } else {
+ // The image resource data is bigger than the I/O buffer.
+ LFA_Seek ( fileRef, (ioBuf.filePos + (ioBuf.ptr - ioBuf.data)), SEEK_SET );
+ LFA_Read ( fileRef, (void*)rsrcPtr->dataPtr, dataLen );
+ FillBuffer ( fileRef, nextRsrcPos, &ioBuf );
}
}
@@ -461,7 +457,7 @@ XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr )
// Parse the rebuilt image resource block. This is the easiest way to reconstruct the map.
this->ParseMemoryResources ( newContent, newLength, false );
- this->ownedContent = true; // ! We really do own it.
+ this->ownedContent = (newLength > 0); // ! We really do own the new content, if not empty.
if ( dataPtr != 0 ) *dataPtr = newContent;
return newLength;
@@ -476,16 +472,15 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File
IOBuffer * ioBuf, XMP_AbortProc abortProc, void * abortArg )
{
IgnoreParam(ioBuf);
+ const XMP_Uns32 zero32 = 0;
const bool checkAbort = (abortProc != 0);
struct RsrcHeader {
XMP_Uns32 type;
XMP_Uns16 id;
- XMP_Uns16 name;
- XMP_Uns32 dataLen;
};
- XMP_Assert ( sizeof(RsrcHeader) == 12 );
+ XMP_Assert ( (offsetof(RsrcHeader,type) == 0) && (offsetof(RsrcHeader,id) == 4) );
if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure );
@@ -509,12 +504,10 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File
#endif
// First write all of the '8BIM' resources from the map. Use the internal data if present, else
- // copy the data from the file. We don't preserve names for these resources, but then Photoshop
- // itself tosses out all resource names and has no plans to ever support them.
+ // copy the data from the file.
- RsrcHeader outRsrc;
- outRsrc.type = MakeUns32BE ( k8BIM );
- outRsrc.name = 0;
+ RsrcHeader outHeader;
+ outHeader.type = MakeUns32BE ( k8BIM );
InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.begin();
InternalRsrcMap::iterator rsrcEnd = this->imgRsrcs.end();
@@ -524,9 +517,23 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File
InternalRsrcInfo& currRsrc = rsrcPos->second;
- outRsrc.id = MakeUns16BE ( currRsrc.id );
- outRsrc.dataLen = MakeUns32BE ( currRsrc.dataLen );
- LFA_Write ( destRef, &outRsrc, sizeof(RsrcHeader) );
+ outHeader.id = MakeUns16BE ( currRsrc.id );
+ LFA_Write ( destRef, &outHeader, 6 );
+ destLength += 6;
+
+ if ( currRsrc.rsrcName == 0 ) {
+ LFA_Write ( destRef, &zero32, 2 );
+ destLength += 2;
+ } else {
+ XMP_Assert ( currRsrc.rsrcName[0] != 0 );
+ XMP_Uns16 nameLen = currRsrc.rsrcName[0]; // ! Include room for +1.
+ XMP_Uns16 paddedLen = (nameLen + 2) & 0xFFFE; // ! Round up to an even total. Yes, +2!
+ LFA_Write ( destRef, currRsrc.rsrcName, paddedLen );
+ destLength += paddedLen;
+ }
+
+ XMP_Uns32 dataLen = MakeUns32BE ( currRsrc.dataLen );
+ LFA_Write ( destRef, &dataLen, 4 );
// printf ( " #%d, offset %d (0x%X), dataLen %d\n", currRsrc.id, destLength, destLength, currRsrc.dataLen );
if ( currRsrc.dataPtr != 0 ) {
@@ -536,10 +543,10 @@ XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( LFA_FileRef sourceRef, LFA_File
LFA_Copy ( sourceRef, destRef, currRsrc.dataLen );
}
- destLength += sizeof(RsrcHeader) + currRsrc.dataLen;
+ destLength += 4 + currRsrc.dataLen;
if ( (currRsrc.dataLen & 1) != 0 ) {
- LFA_Write ( destRef, &outRsrc.name, 1 ); // ! The name contains zero.
+ LFA_Write ( destRef, &zero32, 1 ); // ! Pad the data to an even length.
++destLength;
}
diff --git a/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp b/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp
index 027162c..592f3e2 100644
--- a/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp
+++ b/source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp
@@ -10,6 +10,8 @@
#include "PSIR_Support.hpp"
#include "EndianUtils.hpp"
+#include <string.h>
+
// =================================================================================================
/// \file PSIR_MemoryReader.cpp
/// \brief Implementation of the memory-based read-only form of PSIR_Manager.
@@ -79,7 +81,7 @@ void PSIR_MemoryReader::ParseMemoryResources ( const void* data, XMP_Uns32 lengt
XMP_Uns32 dataLen = GetUns32BE(psirPtr);
psirPtr += 4; // Advance to the resource data.
- XMP_Uns32 psirOffset = psirPtr - this->psirContent;
+ XMP_Uns32 psirOffset = (XMP_Uns32) (psirPtr - this->psirContent);
if ( (dataLen > length) || (psirPtr > psirEnd-dataLen) ) break; // Bad image resource. Throw instead?
diff --git a/source/XMPFiles/FormatSupport/PSIR_Support.hpp b/source/XMPFiles/FormatSupport/PSIR_Support.hpp
index 91d064b..a651bef 100644
--- a/source/XMPFiles/FormatSupport/PSIR_Support.hpp
+++ b/source/XMPFiles/FormatSupport/PSIR_Support.hpp
@@ -3,7 +3,7 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2006-2007 Adobe Systems Incorporated
+// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -187,6 +187,10 @@ public:
private:
+ // Memory usage notes: PSIR_MemoryReader is for memory-based read-only usage (both apply). There
+ // is no need to ever allocate separate blocks of memory, everything is used directly from the
+ // PSIR stream.
+
bool ownedContent;
XMP_Uns32 psirLength;
@@ -229,33 +233,63 @@ public:
XMP_Uns32 UpdateFileResources ( LFA_FileRef sourceRef, LFA_FileRef destRef,
IOBuffer * ioBuf, XMP_AbortProc abortProc, void * abortArg );
- PSIR_FileWriter() : changed(false), memParsed(false), fileParsed(false),
+ PSIR_FileWriter() : changed(false), legacyDeleted(false), memParsed(false), fileParsed(false),
ownedContent(false), memLength(0), memContent(0) {};
virtual ~PSIR_FileWriter();
+ // Memory usage notes: PSIR_FileWriter is for file-based OR read/write usage. For memory-based
+ // streams the dataPtr and rsrcName are initially into the stream, they become a separate
+ // allocation if changed. For file-based streams they are always a separate allocation.
+
+ // ! The working data values are always big endian, no matter where stored. It is the client's
+ // ! responsibility to flip them as necessary.
+
+ static const bool kIsFileBased = true; // For use in the InternalRsrcInfo constructor.
+ static const bool kIsMemoryBased = false;
+
struct InternalRsrcInfo {
+ public:
+
bool changed;
+ bool fileBased;
XMP_Uns16 id;
XMP_Uns32 dataLen;
- void* dataPtr; // ! The data is read-only! Null if the value is not captured!
+ void* dataPtr; // ! Null if the value is not captured!
XMP_Uns32 origOffset; // The offset (at parse time) of the resource data.
- XMP_Uns8* rsrcName; // ! A Pascal string. Only in the map for memory-based resources.
- InternalRsrcInfo() : changed(false), id(0), dataLen(0), dataPtr(0), origOffset(0), rsrcName(0) {};
- InternalRsrcInfo ( XMP_Uns16 _id, XMP_Uns32 _dataLen, void* _dataPtr, XMP_Uns32 _origOffset )
- : changed(false), id(_id), dataLen(_dataLen), dataPtr(_dataPtr), origOffset(_origOffset), rsrcName(0) {};
- ~InternalRsrcInfo()
- {
- if ( this->changed && (this->dataPtr != 0) ) free ( this->dataPtr );
- };
+ XMP_Uns8* rsrcName; // ! A Pascal string, leading length byte, no nul terminator!
+
+ inline void FreeData() {
+ if ( this->fileBased || this->changed ) {
+ if ( this->dataPtr != 0 ) { free ( this->dataPtr ); this->dataPtr = 0; }
+ }
+ }
+
+ inline void FreeName() {
+ if ( this->fileBased || this->changed ) {
+ if ( this->rsrcName != 0 ) { free ( this->rsrcName ); this->rsrcName = 0; }
+ }
+ }
+
+ InternalRsrcInfo ( XMP_Uns16 _id, XMP_Uns32 _dataLen, bool _fileBased )
+ : changed(false), fileBased(_fileBased), id(_id), dataLen(_dataLen), dataPtr(0),
+ origOffset(0), rsrcName(0) {};
+ ~InternalRsrcInfo() { this->FreeData(); this->FreeName(); };
+
void operator= ( const InternalRsrcInfo & in )
- { // ! Hack to transfer ownership of the data block.
- this->changed = in.changed;
- this->id = in.id;
- this->dataLen = in.dataLen; this->dataPtr = in.dataPtr;
- this->origOffset = in.origOffset; this->rsrcName = in.rsrcName;
- *((void**)&in.dataPtr) = 0;
+ {
+ // ! Gag! Transfer ownership of the dataPtr and rsrcName!
+ this->FreeData();
+ memcpy ( this, &in, sizeof(*this) ); // AUDIT: Use of sizeof(InternalRsrcInfo) is safe.
+ *((void**)&in.dataPtr) = 0; // The pointer is now owned by "this".
+ *((void**)&in.rsrcName) = 0; // The pointer is now owned by "this".
};
+
+ private:
+
+ InternalRsrcInfo() // Hidden on purpose, fileBased must be properly set.
+ : changed(false), fileBased(false), id(0), dataLen(0), dataPtr(0), origOffset(0), rsrcName(0) {};
+
};
// The origOffset is the absolute file offset for file parses, the memory block offset for
@@ -263,7 +297,7 @@ public:
private:
- bool changed;
+ bool changed, legacyDeleted;
bool memParsed, fileParsed;
bool ownedContent;
diff --git a/source/XMPFiles/FormatSupport/QuickTime_Support.cpp b/source/XMPFiles/FormatSupport/QuickTime_Support.cpp
index 259a9dc..8ba1221 100644
--- a/source/XMPFiles/FormatSupport/QuickTime_Support.cpp
+++ b/source/XMPFiles/FormatSupport/QuickTime_Support.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -8,6 +8,8 @@
// =================================================================================================
#include "XMP_Environment.h"
+#if ! ( XMP_64 || XMP_UNIXBuild) // Closes at very bottom.
+
#include "QuickTime_Support.hpp"
#if XMP_MacBuild
@@ -24,10 +26,15 @@ namespace QuickTime_Support
// =============================================================================================
- bool MainInitialize()
+ bool MainInitialize ( bool ignoreInit )
{
OSStatus err = noErr;
-
+
+ if ( ignoreInit ) {
+ sMainInitOK = true;
+ return true;
+ }
+
#if XMP_WinBuild
err = ::InitializeQTML ( 0 );
#endif
@@ -41,8 +48,11 @@ namespace QuickTime_Support
// =============================================================================================
- void MainTerminate()
+ void MainTerminate ( bool ignoreInit )
{
+
+ if ( ignoreInit ) return;
+
::ExitMovies();
#if XMP_WinBuild
@@ -77,3 +87,5 @@ namespace QuickTime_Support
} // ThreadTerminate
} // namespace QuickTime_Support
+
+#endif
diff --git a/source/XMPFiles/FormatSupport/QuickTime_Support.hpp b/source/XMPFiles/FormatSupport/QuickTime_Support.hpp
index fc265ab..24f903d 100644
--- a/source/XMPFiles/FormatSupport/QuickTime_Support.hpp
+++ b/source/XMPFiles/FormatSupport/QuickTime_Support.hpp
@@ -10,18 +10,20 @@
// of the Adobe license agreement accompanying it.
// =================================================================================================
-#include "XMP_Environment.h" // ! This must be the first include.
+#include "XMP_Environment.h" // ! This must be the first include.
+#if ! ( XMP_64 || XMP_UNIXBuild) // Closes at very bottom.
namespace QuickTime_Support
{
extern bool sMainInitOK;
- bool MainInitialize(); // For the main thread.
- void MainTerminate();
+ bool MainInitialize ( bool ignoreInit ); // For the main thread.
+ void MainTerminate ( bool ignoreInit );
bool ThreadInitialize(); // For background threads.
void ThreadTerminate();
} // namespace QuickTime_Support
+#endif // XMP_64 || XMP_UNIXBuild
#endif // __QuickTime_Support_hpp__
diff --git a/source/XMPFiles/FormatSupport/RIFF_Support.cpp b/source/XMPFiles/FormatSupport/RIFF_Support.cpp
index 3b25568..9574119 100644
--- a/source/XMPFiles/FormatSupport/RIFF_Support.cpp
+++ b/source/XMPFiles/FormatSupport/RIFF_Support.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2002-2007 Adobe Systems Incorporated
+// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -9,6 +9,10 @@
#include "RIFF_Support.hpp"
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
namespace RIFF_Support {
#define ckidPremierePadding MakeFourCC ('J','U','N','Q')
@@ -27,7 +31,7 @@ namespace RIFF_Support {
} atag;
// Local function declarations
- static bool ReadTag ( LFA_FileRef inFileRef, long * outTag, UInt32 * outLength, long * subtype, UInt64 & inOutPosition );
+ static bool ReadTag ( LFA_FileRef inFileRef, long * outTag, UInt32 * outLength, long * subtype, UInt64 & inOutPosition, UInt64 maxOffset );
static void AddTag ( RiffState & inOutRiffState, long tag, UInt32 len, UInt64 & inOutPosition, long parentID, long parentnum, long subtypeID );
static long SubRead ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long parentid, UInt32 parentlen, UInt64 & inOutPosition );
static bool ReadChunk ( LFA_FileRef inFileRef, UInt64 & pos, UInt32 len, char * outBuffer );
@@ -225,6 +229,13 @@ namespace RIFF_Support {
LFA_Seek ( inFileRef, 0, SEEK_END );
pos = GetFilePosition ( inFileRef );
+
+ if ( (pos & 1) == 1 ) {
+ // The file length is odd, need a pad byte.
+ XMP_Uns8 pad = 0;
+ LFA_Write ( inFileRef, &pad, 1 );
+ ++pos;
+ }
if ( avail < len ) {
@@ -283,79 +294,115 @@ namespace RIFF_Support {
UInt64 pos = 0;
long tag, subtype;
UInt32 len;
-
+
+ const XMP_Int64 fileLen = LFA_Measure ( inFileRef );
+ if ( fileLen < 8 ) return 0;
+
LFA_Seek ( inFileRef, 0, SEEK_SET );
- // read first tag (always RIFFtype)
- while ( ReadTag ( inFileRef, &tag, &len, &subtype, pos) ) {
+ while ( ReadTag ( inFileRef, &tag, &len, &subtype, pos, fileLen ) ) {
if ( tag != FOURCC_RIFF ) break;
AddTag ( inOutRiffState, tag, len, pos, 0, 0, subtype );
if ( subtype != 0 ) SubRead ( inFileRef, inOutRiffState, subtype, len, pos );
}
- return inOutRiffState.tags.size();
+ return (long) inOutRiffState.tags.size();
}
// =============================================================================================
- static bool ReadTag ( LFA_FileRef inFileRef, long * outTag, UInt32 * outLength, long * subtype, UInt64 & inOutPosition )
+ static bool ReadTag ( LFA_FileRef inFileRef, long * outTag, UInt32 * outLength, long * subtype, UInt64 & inOutPosition, UInt64 maxOffset )
{
UInt32 realLength;
- try {
+ long bytesRead;
+ bytesRead = LFA_Read ( inFileRef, outTag, 4 );
+ if ( bytesRead != 4 ) return false;
+ *outTag = GetUns32LE ( outTag );
+
+ bytesRead = LFA_Read ( inFileRef, outLength, 4 );
+ if ( bytesRead != 4 ) return false;
+ *outLength = GetUns32LE ( outLength );
+
+ realLength = *outLength;
+ realLength += (realLength & 1); // Round up to an even value.
+
+ inOutPosition = GetFilePosition ( inFileRef ); // The file offset of the data portion.
+ UInt64 maxLength = maxOffset - inOutPosition;
+
+ if ( (inOutPosition > maxOffset) || ((UInt64)(*outLength) > maxLength) ) {
+
+ bool ignoreLastPad = true; // Ignore cases where a final pad byte is missing.
+ UInt64 fileLen = LFA_Measure ( inFileRef );
+ if ( inOutPosition > (maxOffset + 1) ) ignoreLastPad = false;
+ if ( (UInt64)(*outLength) > (maxLength + 1) ) ignoreLastPad = false;
+
+ if ( ! ignoreLastPad ) {
+
+ // Workaround for bad files in the field that have a bad size in the outermost RIFF
+ // chunk. Do a "runtime repair" of cases where the length is too long (beyond EOF).
+ // This handles read-only usage, update usage is repaired (or not) in the handler.
+
+ bool oversizeRIFF = (inOutPosition == 8) && // Is this the initial 'RIFF' chunk?
+ (fileLen >= 8); // Is the file at least of the minimal size?
+
+ if ( ! oversizeRIFF ) {
+ XMP_Throw ( "RIFF tag exceeds maximum length", kXMPErr_BadValue );
+ } else {
+ *outLength = (UInt32)(fileLen) - 8;
+ realLength = *outLength;
+ realLength += (realLength & 1); // Round up to an even value.
+ }
- long bytesRead;
- bytesRead = LFA_Read ( inFileRef, outTag, 4 );
- if ( bytesRead == 0 ) return false;
- *outTag = GetUns32LE ( outTag );
-
- bytesRead = LFA_Read ( inFileRef, outLength, 4 );
- if ( bytesRead == 0 ) return false;
- *outLength = GetUns32LE ( outLength );
-
- realLength = *outLength;
- realLength += (realLength & 1); // round up to words
-
- *subtype = 0;
-
- if ( (*outTag != FOURCC_LIST) && (*outTag != FOURCC_RIFF) ) {
+ }
- inOutPosition = GetFilePosition ( inFileRef );
- UInt64 tempPos = inOutPosition + realLength;
+ }
+
+ *subtype = 0;
+
+ if ( (*outTag != FOURCC_LIST) && (*outTag != FOURCC_RIFF) ) {
+
+ UInt64 tempPos = inOutPosition + realLength;
+ if ( tempPos <= maxOffset ) {
LFA_Seek ( inFileRef, tempPos, SEEK_SET );
+ } else if ( (tempPos == (maxOffset + 1)) && (maxOffset == (UInt64)LFA_Measure(inFileRef)) ) {
+ LFA_Seek ( inFileRef, 0, SEEK_END ); // Hack to tolerate a missing final pad byte.
+ } else {
+ XMP_Throw ( "Bad RIFF offset", kXMPErr_BadValue );
+ }
- } else {
+ } else {
- bytesRead = LFA_Read ( inFileRef, subtype, 4 );
- if ( bytesRead == 0 ) return false;
- *subtype = GetUns32LE ( subtype );
+ bytesRead = LFA_Read ( inFileRef, subtype, 4 );
+ if ( bytesRead != 4 ) return false;
+ *subtype = GetUns32LE ( subtype );
- *outLength -= 4;
- realLength -= 4;
-
- // Special case:
- // Since the 'movi' chunk can contain billions of subchunks, skip over the 'movi' subchunk.
- //
- // The 'movi' subtype is added to the list as the TAG.
- // The subtype is returned empty so nobody will try to parse the subchunks.
+ *outLength -= 4;
+ realLength -= 4;
- if ( *subtype == listtypeAVIMOVIE ) {
- inOutPosition = GetFilePosition ( inFileRef );
- UInt64 tempPos = inOutPosition + realLength;
- LFA_Seek ( inFileRef, tempPos, SEEK_SET );
- *outLength += 4;
- *outTag = *subtype;
- *subtype = 0;
- }
+ // Special case:
+ // Since the 'movi' chunk can contain billions of subchunks, skip over the 'movi' subchunk.
+ //
+ // The 'movi' subtype is added to the list as the TAG.
+ // The subtype is returned empty so nobody will try to parse the subchunks.
+ if ( *subtype == listtypeAVIMOVIE ) {
inOutPosition = GetFilePosition ( inFileRef );
-
+ UInt64 tempPos = inOutPosition + realLength;
+ if ( tempPos <= maxOffset ) {
+ LFA_Seek ( inFileRef, tempPos, SEEK_SET );
+ } else if ( (tempPos == (maxOffset + 1)) && (maxOffset == (UInt64)LFA_Measure(inFileRef)) ) {
+ LFA_Seek ( inFileRef, 0, SEEK_END ); // Hack to tolerate a missing final pad byte.
+ } else {
+ XMP_Throw ( "Bad RIFF offset", kXMPErr_BadValue );
+ }
+ *outLength += 4;
+ *outTag = *subtype;
+ *subtype = 0;
}
- } catch ( ... ) {
-
- return false;
+ inOutPosition = GetFilePosition ( inFileRef );
}
@@ -396,14 +443,16 @@ namespace RIFF_Support {
UInt64 oldpos;
total = 0;
- parentnum = inOutRiffState.tags.size() - 1;
+ parentnum = (long) inOutRiffState.tags.size() - 1;
+
+ UInt64 maxOffset = inOutPosition + parentlen;
while ( parentlen > 0 ) {
oldpos = inOutPosition;
- ReadTag ( inFileRef, &tag, &len, &subtype, inOutPosition );
+ ReadTag ( inFileRef, &tag, &len, &subtype, inOutPosition, maxOffset );
AddTag ( inOutRiffState, tag, len, inOutPosition, parentid, parentnum, subtype );
- len += (len & 1);
+ len += (len & 1); //padding byte
if ( subtype == 0 ) {
childlen = 8 + len;
@@ -424,7 +473,8 @@ namespace RIFF_Support {
// =============================================================================================
bool GetRIFFChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long tagID,
- long parentID, long subtypeID, char * outBuffer, unsigned long * outBufferSize )
+ long parentID, long subtypeID, char * outBuffer, unsigned long * outBufferSize,
+ UInt64* posPtr )
{
UInt32 len;
UInt64 pos;
@@ -432,16 +482,19 @@ namespace RIFF_Support {
bool found = FindChunk ( inOutRiffState, tagID, parentID, subtypeID, 0, &len, &pos );
if ( ! found ) return false;
+ if ( posPtr != 0 )
+ *posPtr = pos; // return position CBR
+
if ( outBuffer == 0 ) {
*outBufferSize = (unsigned long)len;
return true; // Found, but not wanted.
}
- if ( len > *outBufferSize ) len = *outBufferSize;
-
+ if ( len > *outBufferSize )
+ len = *outBufferSize;
found = ReadChunk ( inFileRef, pos, len, outBuffer );
- return found;
-
+
+ return found;
}
// =============================================================================================
@@ -489,3 +542,310 @@ namespace RIFF_Support {
}
} // namespace RIFF_Support
+
+// =================================================================================================
+
+// *** Could be moved to a separate header
+
+#pragma pack(push,1)
+
+// [TODO] Can we switch to using just a full path here?
+struct FSSpecLegacy
+{
+ short vRefNum;
+ long parID;
+ char name[260]; // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp -- 260 is "old school", 32000 is "new school".
+};
+
+struct CR8R_CreatorAtom
+{
+ unsigned long magicLu;
+
+ long atom_sizeL; // Size of this structure.
+ short atom_vers_majorS; // Major atom version.
+ short atom_vers_minorS; // Minor atom version.
+
+ // mac
+ unsigned long creator_codeLu; // Application code on MacOS.
+ unsigned long creator_eventLu; // Invocation appleEvent.
+
+ // windows
+ char creator_extAC[16]; // Extension allowing registry search to app.
+ char creator_flagAC[16]; // Flag passed to app at invocation time.
+
+ char creator_nameAC[32]; // Name of the creator application.
+};
+
+typedef CR8R_CreatorAtom** CR8R_CreatorAtomHandle;
+
+#define PR_PROJECT_LINK_MAGIC 0x600DF00D // GoodFood
+
+typedef enum
+{
+ Embed_ExportTypeMovie = 0,
+ Embed_ExportTypeStill,
+ Embed_ExportTypeAudio,
+ Embed_ExportTypeCustom
+}
+Embed_ExportType;
+
+
+struct Embed_ProjectLinkAtom
+{
+ unsigned long magicLu;
+ long atom_sizeL;
+ short atom_vers_apiS;
+ short atom_vers_codeS;
+ unsigned long exportType; // See enum. The type of export that generated the file
+ FSSpecLegacy fullPath; // Full path of the project file
+};
+
+#pragma pack(pop)
+
+// -------------------------------------------------------------------------------------------------
+
+#define kCreatorTool "CreatorTool"
+#define AdobeCreatorAtomVersion_Major 1
+#define AdobeCreatorAtomVersion_Minor 0
+#define AdobeCreatorAtom_Magic 0xBEEFCAFE
+
+#define myCreatorAtom MakeFourCC ( 'C','r','8','r' )
+
+static void CreatorAtom_Initialize ( CR8R_CreatorAtom& creatorAtom )
+{
+ memset ( &creatorAtom, 0, sizeof(CR8R_CreatorAtom) );
+ creatorAtom.magicLu = AdobeCreatorAtom_Magic;
+ creatorAtom.atom_vers_majorS = AdobeCreatorAtomVersion_Major;
+ creatorAtom.atom_vers_minorS = AdobeCreatorAtomVersion_Minor;
+ creatorAtom.atom_sizeL = sizeof(CR8R_CreatorAtom);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static void CreatorAtom_MakeValid ( CR8R_CreatorAtom * creator_atomP )
+{
+ // If already valid, no conversion is needed.
+ if ( creator_atomP->magicLu == AdobeCreatorAtom_Magic ) return;
+
+ Flip4 ( &creator_atomP->magicLu );
+ Flip2 ( &creator_atomP->atom_vers_majorS );
+ Flip2 ( &creator_atomP->atom_vers_minorS );
+
+ Flip4 ( &creator_atomP->atom_sizeL );
+ Flip4 ( &creator_atomP->creator_codeLu );
+ Flip4 ( &creator_atomP->creator_eventLu );
+
+ XMP_Assert ( creator_atomP->magicLu == AdobeCreatorAtom_Magic );
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static void CreatorAtom_ToBE ( CR8R_CreatorAtom * creator_atomP )
+{
+ creator_atomP->atom_vers_majorS = MakeUns16BE ( creator_atomP->atom_vers_majorS );
+ creator_atomP->atom_vers_minorS = MakeUns16BE ( creator_atomP->atom_vers_minorS );
+
+ creator_atomP->magicLu = MakeUns32BE ( creator_atomP->magicLu );
+ creator_atomP->atom_sizeL = MakeUns32BE ( creator_atomP->atom_sizeL );
+ creator_atomP->creator_codeLu = MakeUns32BE ( creator_atomP->creator_codeLu );
+ creator_atomP->creator_eventLu = MakeUns32BE ( creator_atomP->creator_eventLu );
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static void ProjectLinkAtom_MakeValid ( Embed_ProjectLinkAtom * link_atomP )
+{
+ // If already valid, no conversion is needed.
+ if ( link_atomP->magicLu == PR_PROJECT_LINK_MAGIC ) return;
+
+ // do the header
+ Flip4 ( &link_atomP->magicLu );
+ Flip4 ( &link_atomP->atom_sizeL );
+ Flip2 ( &link_atomP->atom_vers_apiS );
+ Flip2 ( &link_atomP->atom_vers_codeS );
+
+ // do the FSSpec data
+ Flip2 ( &link_atomP->fullPath.vRefNum );
+ Flip4 ( &link_atomP->fullPath.parID );
+
+ XMP_Assert ( link_atomP->magicLu == PR_PROJECT_LINK_MAGIC );
+}
+
+// -------------------------------------------------------------------------------------------------
+
+static std::string CharsToString ( const char* buffer, int maxBuffer )
+{
+ // convert possibly non-zero terminated char buffer to std::string
+ std::string result;
+
+ char bufferz[256];
+ XMP_Assert ( maxBuffer < 256 );
+ if ( maxBuffer >= 256 ) return result;
+
+ memcpy ( bufferz, buffer, maxBuffer );
+ bufferz[maxBuffer] = 0;
+
+ result = bufferz;
+ return result;
+
+}
+
+// -------------------------------------------------------------------------------------------------
+
+bool CreatorAtom::Import ( SXMPMeta& xmpObj,
+ LFA_FileRef fileRef,
+ RIFF_Support::RiffState& riffState )
+{
+ static const long myProjectLink = MakeFourCC ( 'P','r','m','L' );
+
+ unsigned long projectLinkSize;
+ bool ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myProjectLink, 0, 0, 0, &projectLinkSize );
+ if ( ok ) {
+
+ Embed_ProjectLinkAtom epla;
+
+ std::string projectPathString;
+ RIFF_Support::GetRIFFChunk ( fileRef, riffState, myProjectLink, 0, 0, (char*) &epla, &projectLinkSize );
+ if ( ok ) {
+ ProjectLinkAtom_MakeValid ( &epla );
+ projectPathString = epla.fullPath.name;
+ }
+
+ if ( ! projectPathString.empty() ) {
+
+ if ( projectPathString[0] == '/' ) {
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom",
+ kXMP_NS_CreatorAtom, "posixProjectPath", projectPathString, 0 );
+ } else if ( projectPathString.substr(0,4) == std::string("\\\\?\\") ) {
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom",
+ kXMP_NS_CreatorAtom, "uncProjectPath", projectPathString, 0 );
+ }
+
+ std::string projectTypeString;
+ switch ( epla.exportType ) {
+ case Embed_ExportTypeMovie : projectTypeString = "movie"; break;
+ case Embed_ExportTypeStill : projectTypeString = "still"; break;
+ case Embed_ExportTypeAudio : projectTypeString = "audio"; break;
+ case Embed_ExportTypeCustom : projectTypeString = "custom"; break;
+ }
+
+ if ( ! projectTypeString.empty() ) {
+ xmpObj.SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", projectTypeString.c_str() );
+ }
+
+ }
+
+ }
+
+ unsigned long creatorAtomSize = 0;
+ ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, 0, &creatorAtomSize );
+ if ( ok ) {
+
+ CR8R_CreatorAtom creatorAtom;
+ ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, (char*) &creatorAtom, &creatorAtomSize );
+
+ if ( ok ) {
+
+ CreatorAtom_MakeValid ( &creatorAtom );
+
+ char buffer[256];
+ std::string xmpString;
+
+ sprintf ( buffer, "%d", creatorAtom.creator_codeLu );
+ xmpString = buffer;
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", xmpString, 0 );
+
+ sprintf ( buffer, "%d", creatorAtom.creator_eventLu );
+ xmpString = buffer;
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", xmpString, 0 );
+
+ xmpString = CharsToString ( creatorAtom.creator_extAC, sizeof(creatorAtom.creator_extAC) );
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", xmpString, 0 );
+
+ xmpString = CharsToString ( creatorAtom.creator_flagAC, sizeof(creatorAtom.creator_flagAC) );
+ xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", xmpString, 0 );
+
+ xmpString = CharsToString ( creatorAtom.creator_nameAC, sizeof(creatorAtom.creator_nameAC) );
+ xmpObj.SetProperty ( kXMP_NS_XMP, "CreatorTool", xmpString, 0 );
+
+ }
+
+ }
+
+ return ok;
+
+}
+
+// -------------------------------------------------------------------------------------------------
+
+// *** Not in C library:
+#ifndef min
+ #define min(a,b) ( (a < b) ? a : b )
+#endif
+
+#define EnsureFinalNul(buffer) buffer [ sizeof(buffer) - 1 ] = 0
+
+bool CreatorAtom::Update ( SXMPMeta& xmpObj,
+ LFA_FileRef fileRef,
+ long riffType,
+ RIFF_Support::RiffState& riffState )
+{
+
+ // Creator Atom related values.
+ bool found = false;
+ std::string posixPathString, uncPathString;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "posixProjectPath", &posixPathString, 0 ) ) found = true;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "uncProjectPath", &uncPathString, 0 ) ) found = true;
+
+ std::string applicationCodeString, invocationAppleEventString, extensionString, invocationFlagsString, creatorToolString;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &applicationCodeString, 0 ) ) found = true;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &invocationAppleEventString, 0 ) ) found = true;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &extensionString, 0 ) ) found = true;
+ if ( xmpObj.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &invocationFlagsString, 0 ) ) found = true;
+ if ( xmpObj.GetProperty ( kXMP_NS_XMP, "CreatorTool", &creatorToolString, 0 ) ) found = true;
+
+ // No Creator Atom information present.
+ if ( ! found ) return true;
+
+ // Read Legacy Creator Atom.
+ unsigned long creatorAtomSize = 0;
+ CR8R_CreatorAtom creatorAtomLegacy;
+ CreatorAtom_Initialize ( creatorAtomLegacy );
+ bool ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, 0, &creatorAtomSize );
+ if ( ok ) {
+ XMP_Assert ( creatorAtomSize == sizeof(CR8R_CreatorAtom) );
+ ok = RIFF_Support::GetRIFFChunk ( fileRef, riffState, myCreatorAtom, 0, 0, (char*) &creatorAtomLegacy, &creatorAtomSize );
+ CreatorAtom_MakeValid ( &creatorAtomLegacy );
+ }
+
+ // Generate new Creator Atom from XMP.
+ CR8R_CreatorAtom creatorAtomViaXMP;
+ CreatorAtom_Initialize ( creatorAtomViaXMP );
+ if ( ! applicationCodeString.empty() ) {
+ creatorAtomViaXMP.creator_codeLu = strtoul ( applicationCodeString.c_str(), 0, 0 );
+ }
+ if ( ! invocationAppleEventString.empty() ) {
+ creatorAtomViaXMP.creator_eventLu = strtoul ( invocationAppleEventString.c_str(), 0, 0 );
+ }
+ if ( ! extensionString.empty() ) {
+ strncpy ( creatorAtomViaXMP.creator_extAC, extensionString.c_str(), sizeof(creatorAtomViaXMP.creator_extAC) );
+ EnsureFinalNul ( creatorAtomViaXMP.creator_extAC );
+ }
+ if ( ! invocationFlagsString.empty() ) {
+ strncpy ( creatorAtomViaXMP.creator_flagAC, invocationFlagsString.c_str(), sizeof(creatorAtomViaXMP.creator_flagAC) );
+ EnsureFinalNul ( creatorAtomViaXMP.creator_flagAC );
+ }
+ if ( ! creatorToolString.empty() ) {
+ strncpy ( creatorAtomViaXMP.creator_nameAC, creatorToolString.c_str(), sizeof(creatorAtomViaXMP.creator_nameAC) );
+ EnsureFinalNul ( creatorAtomViaXMP.creator_nameAC );
+ }
+
+ // Write new Creator Atom, if necessary.
+ if ( memcmp ( &creatorAtomViaXMP, &creatorAtomLegacy, sizeof(CR8R_CreatorAtom) ) != 0 ) {
+ CreatorAtom_ToBE ( &creatorAtomViaXMP );
+ ok = RIFF_Support::PutChunk ( fileRef, riffState, riffType, myCreatorAtom, (char*)&creatorAtomViaXMP, sizeof(CR8R_CreatorAtom) );
+ }
+
+ return ok;
+
+}
diff --git a/source/XMPFiles/FormatSupport/RIFF_Support.hpp b/source/XMPFiles/FormatSupport/RIFF_Support.hpp
index bb63070..8065e38 100644
--- a/source/XMPFiles/FormatSupport/RIFF_Support.hpp
+++ b/source/XMPFiles/FormatSupport/RIFF_Support.hpp
@@ -116,9 +116,11 @@ namespace RIFF_Support
** will contain the field size if true if returned.
**
** Returns true if the chunk is found.
+ **
+ ** position of chunk _contents_ is returned in postPtr
*/
bool GetRIFFChunk ( LFA_FileRef inFileRef, RiffState & inOutRiffState, long tagID, long parentID,
- long subtypeID, char * outBuffer, unsigned long * outBufferSize );
+ long subtypeID, char * outBuffer, unsigned long * outBufferSize, UInt64* posPtr = 0);
/**
@@ -169,4 +171,23 @@ namespace RIFF_Support
} // namespace RIFF_Support
+// =================================================================================================
+
+// *** Could be moved to a separate header
+
+namespace CreatorAtom {
+
+ bool Import ( SXMPMeta& xmpObj,
+ LFA_FileRef fileRef,
+ RIFF_Support::RiffState& riffState );
+
+ bool Update ( SXMPMeta& xmpObj,
+ LFA_FileRef fileRef,
+ long riffType,
+ RIFF_Support::RiffState& riffState );
+
+}
+
+// =================================================================================================
+
#endif // __RIFF_Support_hpp__
diff --git a/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp b/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp
index d04dbea..a81e8af 100644
--- a/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp
+++ b/source/XMPFiles/FormatSupport/ReconcileIPTC.cpp
@@ -11,6 +11,8 @@
#include "Reconcile_Impl.hpp"
+#include <stdio.h>
+
#if XMP_WinBuild
#pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning)
#pragma warning ( disable : 4996 ) // '...' was declared deprecated
@@ -474,9 +476,17 @@ void ReconcileUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int
import = psir.GetImgRsrc ( kPSIR_CopyrightURL, &rsrcInfo );
if ( import ) import = (! xmp->DoesPropertyExist ( kXMP_NS_XMP_Rights, "WebStatement" ));
if ( import ) {
- std::string utf8;
- ReconcileUtils::LocalToUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen, &utf8 );
- xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", utf8.c_str() );
+ #if ! XMP_UNIXBuild
+ std::string utf8;
+ ReconcileUtils::LocalToUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen, &utf8 );
+ xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", utf8.c_str() );
+ #else
+ // ! Hack until legacy-as-local issues are resolved for generic UNIX.
+ if ( ReconcileUtils::IsUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen ) ) {
+ std::string utf8 ( (char*)rsrcInfo.dataPtr, rsrcInfo.dataLen );
+ xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", utf8.c_str() );
+ }
+ #endif
}
} catch ( ... ) {
// Do nothing, let other imports proceed.
@@ -511,7 +521,7 @@ static void ExportIPTC_Simple ( SXMPMeta * xmp, IPTC_Manager * iptc,
size_t iptcCount = iptc->GetDataSet ( id, 0 );
if ( iptcCount > 1 ) iptc->DeleteDataSet ( id );
- iptc->SetDataSet_UTF8 ( id, value.c_str(), value.size(), 0 ); // ! Don't append a 2nd DataSet!
+ iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), 0 ); // ! Don't append a 2nd DataSet!
} // ExportIPTC_Simple
@@ -544,7 +554,7 @@ static void ExportIPTC_LangAlt ( SXMPMeta * xmp, IPTC_Manager * iptc,
size_t iptcCount = iptc->GetDataSet ( id, 0 );
if ( iptcCount > 1 ) iptc->DeleteDataSet ( id );
- iptc->SetDataSet_UTF8 ( id, value.c_str(), value.size(), 0 ); // ! Don't append a 2nd DataSet!
+ iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), 0 ); // ! Don't append a 2nd DataSet!
} // ExportIPTC_LangAlt
@@ -570,19 +580,19 @@ static void ExportIPTC_Array ( SXMPMeta * xmp, IPTC_Manager * iptc,
if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet?
- size_t xmpCount = xmp->CountArrayItems ( xmpNS, xmpProp );
- size_t iptcCount = iptc->GetDataSet ( id, 0 );
+ XMP_Index xmpCount = xmp->CountArrayItems ( xmpNS, xmpProp );
+ XMP_Index iptcCount = (XMP_Index) iptc->GetDataSet ( id, 0 );
if ( xmpCount != iptcCount ) iptc->DeleteDataSet ( id );
- for ( size_t ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0.
+ for ( XMP_Index ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0.
(void) xmp->GetArrayItem ( xmpNS, xmpProp, ds+1, &value, &xmpFlags );
if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain?
NormalizeToCR ( &value );
- iptc->SetDataSet_UTF8 ( id, value.c_str(), value.size(), ds ); // ! Appends if necessary.
+ iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), ds ); // ! Appends if necessary.
}
@@ -627,7 +637,7 @@ static void ExportIPTC_IntellectualGenre ( SXMPMeta * xmp, IPTC_Manager * iptc,
size_t iptcCount = iptc->GetDataSet ( kIPTC_IntellectualGenre, 0 );
if ( iptcCount > 1 ) iptc->DeleteDataSet ( kIPTC_IntellectualGenre );
- iptc->SetDataSet_UTF8 ( kIPTC_IntellectualGenre, iimValue.c_str(), iimValue.size(), 0 ); // ! Don't append a 2nd DataSet!
+ iptc->SetDataSet_UTF8 ( kIPTC_IntellectualGenre, iimValue.c_str(), (XMP_Uns32)iimValue.size(), 0 ); // ! Don't append a 2nd DataSet!
} // ExportIPTC_IntellectualGenre
@@ -654,12 +664,12 @@ static void ExportIPTC_SubjectCode ( SXMPMeta * xmp, IPTC_Manager * iptc,
if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet?
- size_t xmpCount = xmp->CountArrayItems ( xmpNS, xmpProp );
- size_t iptcCount = iptc->GetDataSet ( kIPTC_SubjectCode, 0 );
+ XMP_Index xmpCount = xmp->CountArrayItems ( xmpNS, xmpProp );
+ XMP_Index iptcCount = (XMP_Index) iptc->GetDataSet ( kIPTC_SubjectCode, 0 );
if ( xmpCount != iptcCount ) iptc->DeleteDataSet ( kIPTC_SubjectCode );
- for ( size_t ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0.
+ for ( XMP_Index ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0.
(void) xmp->GetArrayItem ( xmpNS, xmpProp, ds+1, &xmpValue, &xmpFlags );
if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain?
@@ -669,7 +679,7 @@ static void ExportIPTC_SubjectCode ( SXMPMeta * xmp, IPTC_Manager * iptc,
iimValue += xmpValue;
iimValue += ":::"; // Add the separating colons for the empty name portions.
- iptc->SetDataSet_UTF8 ( kIPTC_SubjectCode, iimValue.c_str(), iimValue.size(), ds ); // ! Appends if necessary.
+ iptc->SetDataSet_UTF8 ( kIPTC_SubjectCode, iimValue.c_str(), (XMP_Uns32)iimValue.size(), ds ); // ! Appends if necessary.
}
@@ -739,6 +749,10 @@ static void ExportIPTC_DateCreated ( SXMPMeta * xmp, IPTC_Manager * iptc,
void ReconcileUtils::ExportIPTC ( SXMPMeta * xmp, IPTC_Manager * iptc )
{
+
+ #if XMP_UNIXBuild
+ return; // ! Hack until the legacy-as-local issues are resolved for generic UNIX.
+ #endif
for ( size_t i = 0; kKnownDataSets[i].id != 255; ++i ) {
@@ -817,9 +831,14 @@ void ReconcileUtils::ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir )
if ( ! found ) {
psir->DeleteImgRsrc ( kPSIR_CopyrightURL );
} else {
- std::string localValue;
- ReconcileUtils::UTF8ToLocal ( utf8Value.c_str(), utf8Value.size(), &localValue );
- psir->SetImgRsrc ( kPSIR_CopyrightURL, localValue.c_str(), localValue.size() );
+ #if ! XMP_UNIXBuild
+ std::string localValue;
+ ReconcileUtils::UTF8ToLocal ( utf8Value.c_str(), utf8Value.size(), &localValue );
+ psir->SetImgRsrc ( kPSIR_CopyrightURL, localValue.c_str(), (XMP_Uns32)localValue.size() );
+ #else
+ // ! Hack until legacy-as-local issues are resolved for generic UNIX.
+ psir->DeleteImgRsrc ( kPSIR_CopyrightURL );
+ #endif
}
} catch ( ... ) {
// Do nothing, let other exports proceed.
diff --git a/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp b/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp
index 00649c9..7c9f1f4 100644
--- a/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp
+++ b/source/XMPFiles/FormatSupport/ReconcileLegacy.cpp
@@ -121,6 +121,16 @@ void ExportXMPtoJTP ( XMP_FileFormat destFormat,
{
XMP_Assert ( xmp != 0 );
XMP_Assert ( (destFormat == kXMP_JPEGFile) || (destFormat == kXMP_TIFFFile) || (destFormat == kXMP_PhotoshopFile) );
+
+ #if XMP_UNIXBuild
+ // ! Hack until the legacy-as-local issues are resolved for generic UNIX.
+ iptc = 0; // Strip IIM from the file.
+ if ( tiff != 0 ) tiff->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_IPTC );
+ if ( psir != 0 ) {
+ psir->DeleteImgRsrc ( kPSIR_IPTC );
+ psir->DeleteImgRsrc ( kPSIR_IPTCDigest );
+ }
+ #endif
// Save the IPTC changed flag specially. SetIPTCDigest will call UpdateMemoryDataSets, which
// will clear the IsChanged flag. Also, UpdateMemoryDataSets can be called twice, once for the
@@ -128,6 +138,19 @@ void ExportXMPtoJTP ( XMP_FileFormat destFormat,
bool iptcChanged = false;
+ // Do not write legacy IPTC (IIM) or PSIR in DNG files (which are a variant of TIFF).
+
+ if ( (destFormat == kXMP_TIFFFile) && (tiff != 0) &&
+ tiff->GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, 0 ) ) {
+
+ iptc = 0; // These prevent calls to ExportIPTC and ExportPSIR.
+ psir = 0;
+
+ tiff->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_IPTC ); // These remove any existing IPTC and PSIR.
+ tiff->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_PSIR );
+
+ }
+
// Export the individual metadata items to the legacy forms. The PSIR and IPTC must be done
// before the TIFF and Exif. The PSIR and IPTC have side effects that can modify the XMP, and
// thus the values written to TIFF and Exif. The side effects are the CR<->LF normalization that
diff --git a/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp b/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp
index 5dbca57..4ae7564 100644
--- a/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp
+++ b/source/XMPFiles/FormatSupport/ReconcileTIFF.cpp
@@ -1,6 +1,6 @@
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
-// Copyright 2006-2007 Adobe Systems Incorporated
+// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
@@ -491,7 +491,11 @@ ImportSingleTIFF_ASCII ( const TIFF_Manager::TagInfo & tagInfo,
if ( isUTF8 ) {
strValue.assign ( chPtr, tagInfo.dataLen );
} else {
- ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue );
+ #if ! XMP_UNIXBuild
+ ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue );
+ #else
+ return; // ! Hack until legacy-as-local issues are resolved for generic UNIX.
+ #endif
}
xmp->SetProperty ( xmpNS, xmpProp, strValue.c_str() );
}
@@ -865,7 +869,11 @@ ImportArrayTIFF_ASCII ( const TIFF_Manager::TagInfo & tagInfo,
if ( isUTF8 ) {
strValue.assign ( chPtr, tagInfo.dataLen );
} else {
- ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue );
+ #if ! XMP_UNIXBuild
+ ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue );
+ #else
+ return; // ! Hack until legacy-as-local issues are resolved for generic UNIX.
+ #endif
}
chPtr = strValue.c_str();
chEnd = chPtr + strValue.size();
@@ -1333,7 +1341,11 @@ ImportTIFF_LocTextASCII ( const TIFF_Manager & tiff, XMP_Uns8 ifd, XMP_Uns16 tag
if ( isUTF8 ) {
strValue.assign ( chPtr, tagInfo.dataLen );
} else {
- ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue );
+ #if ! XMP_UNIXBuild
+ ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue );
+ #else
+ return; // ! Hack