/*****************************************************************************/
// Copyright 2006-2012 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.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_xmp_sdk.cpp#4 $ */
/* $DateTime: 2012/09/05 12:31:51 $ */
/* $Change: 847652 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_xmp_sdk.h"
#include "dng_auto_ptr.h"
#include "dng_assertions.h"
#include "dng_exceptions.h"
#include "dng_flags.h"
#include "dng_host.h"
#include "dng_memory.h"
#include "dng_string.h"
#include "dng_string_list.h"
#include "dng_utils.h"
/*****************************************************************************/
#if qMacOS
#ifndef MAC_ENV
#define MAC_ENV 1
#endif
#endif
#if qWinOS
#ifndef WIN_ENV
#define WIN_ENV 1
#endif
#endif
#include <new>
#include <string>
#define TXMP_STRING_TYPE std::string
#define XMP_INCLUDE_XMPFILES qDNGXMPFiles
#define XMP_StaticBuild 1
#include "XMP.incl_cpp"
/*****************************************************************************/
const char *XMP_NS_TIFF = "http://ns.adobe.com/tiff/1.0/";
const char *XMP_NS_EXIF = "http://ns.adobe.com/exif/1.0/";
const char *XMP_NS_PHOTOSHOP = "http://ns.adobe.com/photoshop/1.0/";
const char *XMP_NS_XAP = "http://ns.adobe.com/xap/1.0/";
const char *XMP_NS_XAP_RIGHTS = "http://ns.adobe.com/xap/1.0/rights/";
const char *XMP_NS_DC = "http://purl.org/dc/elements/1.1/";
const char *XMP_NS_XMP_NOTE = "http://ns.adobe.com/xmp/note/";
const char *XMP_NS_MM = "http://ns.adobe.com/xap/1.0/mm/";
const char *XMP_NS_CRS = "http://ns.adobe.com/camera-raw-settings/1.0/";
const char *XMP_NS_CRSS = "http://ns.adobe.com/camera-raw-saved-settings/1.0/";
const char *XMP_NS_AUX = "http://ns.adobe.com/exif/1.0/aux/";
const char *XMP_NS_LCP = "http://ns.adobe.com/photoshop/1.0/camera-profile";
const char *XMP_NS_IPTC = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/";
const char *XMP_NS_IPTC_EXT = "http://iptc.org/std/Iptc4xmpExt/2008-02-29/";
const char *XMP_NS_CRX = "http://ns.adobe.com/lightroom-settings-experimental/1.0/";
const char *XMP_NS_DNG = "http://ns.adobe.com/dng/1.0/";
/******************************************************************************/
#define CATCH_XMP(routine, fatal)\
\
catch (std::bad_alloc &)\
{\
DNG_REPORT ("Info: XMP " routine " threw memory exception");\
ThrowMemoryFull ();\
}\
\
catch (XMP_Error &error)\
{\
const char *errMessage = error.GetErrMsg ();\
if (errMessage && strlen (errMessage) <= 128)\
{\
char errBuffer [256];\
sprintf (errBuffer, "Info: XMP " routine " threw '%s' exception", errMessage);\
DNG_REPORT ( errBuffer);\
}\
else\
{\
DNG_REPORT ("Info: XMP " routine " threw unnamed exception");\
}\
if (fatal) ThrowProgramError ();\
}\
\
catch (...)\
{\
DNG_REPORT ("Info: XMP " routine " threw unknown exception");\
if (fatal) ThrowProgramError ();\
}
/*****************************************************************************/
class dng_xmp_private
{
public:
SXMPMeta *fMeta;
dng_xmp_private ()
: fMeta (NULL)
{
}
dng_xmp_private (const dng_xmp_private &xmp);
~dng_xmp_private ()
{
if (fMeta)
{
delete fMeta;
}
}
private:
// Hidden assignment operator.
dng_xmp_private & operator= (const dng_xmp_private &xmp);
};
/*****************************************************************************/
dng_xmp_private::dng_xmp_private (const dng_xmp_private &xmp)
: fMeta (NULL)
{
if (xmp.fMeta)
{
fMeta = new SXMPMeta (xmp.fMeta->Clone (0));
if (!fMeta)
{
ThrowMemoryFull ();
}
}
}
/*****************************************************************************/
dng_xmp_sdk::dng_xmp_sdk ()
: fPrivate (NULL)
{
fPrivate = new dng_xmp_private;
if (!fPrivate)
{
ThrowMemoryFull ();
}
}
/*****************************************************************************/
dng_xmp_sdk::dng_xmp_sdk (const dng_xmp_sdk &sdk)
: fPrivate (NULL)
{
fPrivate = new dng_xmp_private (*sdk.fPrivate);
if (!fPrivate)
{
ThrowMemoryFull ();
}
}
/*****************************************************************************/
dng_xmp_sdk::~dng_xmp_sdk ()
{
if (fPrivate)
{
delete fPrivate;
}
}
/*****************************************************************************/
static bool gInitializedXMP = false;
/*****************************************************************************/
void dng_xmp_sdk::InitializeSDK (dng_xmp_namespace * extraNamespaces,
const char *software)
{
if (!gInitializedXMP)
{
try
{
if (!SXMPMeta::Initialize ())
{
ThrowProgramError ();
}
// Register Lightroom beta settings namespace.
// We no longer read this but I don't want to cut it out this close
// to a release. [bruzenak]
{
TXMP_STRING_TYPE ss;
SXMPMeta::RegisterNamespace (XMP_NS_CRX,
"crx",
&ss);
}
// Register CRSS snapshots namespace
{
TXMP_STRING_TYPE ss;
SXMPMeta::RegisterNamespace (XMP_NS_CRSS,
"crss",
&ss);
}
// Register LCP (lens correction profiles) namespace
{
TXMP_STRING_TYPE ss;
SXMPMeta::RegisterNamespace (XMP_NS_LCP,
"stCamera",
&ss);
}
// Register DNG format metadata namespace
{
TXMP_STRING_TYPE ss;
SXMPMeta::RegisterNamespace (XMP_NS_DNG,
"dng",
&ss);
}
// Register extra namespaces.
if (extraNamespaces != NULL)
{
for (; extraNamespaces->fullName != NULL; ++extraNamespaces)
{
TXMP_STRING_TYPE ss;
SXMPMeta::RegisterNamespace (extraNamespaces->fullName,
extraNamespaces->shortName,
&ss);
}
}
#if qDNGXMPFiles
#if qLinux
if (!SXMPFiles::Initialize (kXMPFiles_IgnoreLocalText))
#else
if (!SXMPFiles::Initialize ())
#endif
{
ThrowProgramError ();
}
#endif
#if qDNGXMPDocOps
if (software)
{
SXMPDocOps::SetAppName (software);
}
#else
(void) software;
#endif
}
CATCH_XMP ("Initialization", true)
gInitializedXMP = true;
}
}
/******************************************************************************/
void dng_xmp_sdk::TerminateSDK ()
{
if (gInitializedXMP)
{
try
{
#if qDNGXMPFiles
SXMPFiles::Terminate ();
#endif
SXMPMeta::Terminate ();
}
catch (...)
{
}
gInitializedXMP = false;
}
}
/******************************************************************************/
bool dng_xmp_sdk::HasMeta () const
{
if (fPrivate->fMeta)
{
return true;
}
return false;
}
/******************************************************************************/
void dng_xmp_sdk::ClearMeta ()
{
if (HasMeta ())
{
delete fPrivate->fMeta;
fPrivate->fMeta = NULL;
}
}
/******************************************************************************/
void dng_xmp_sdk::MakeMeta ()
{
ClearMeta ();
InitializeSDK ();
try
{
fPrivate->fMeta = new SXMPMeta;
if (!fPrivate->fMeta)
{
ThrowMemoryFull ();
}
}
CATCH_XMP ("MakeMeta", true)
}
/******************************************************************************/
void dng_xmp_sdk::NeedMeta ()
{
if (!HasMeta ())
{
MakeMeta ();
}
}
/******************************************************************************/
void * dng_xmp_sdk::GetPrivateMeta ()
{
NeedMeta ();
return (void *) fPrivate->fMeta;
}
/******************************************************************************/
void dng_xmp_sdk::Parse (dng_host &host,
const char *buffer,
uint32 count)
{
MakeMeta ();
try
{
try
{
fPrivate->fMeta->ParseFromBuffer (buffer, count);
}
CATCH_XMP ("ParseFromBuffer", true)
}
catch (dng_exception &except)
{
ClearMeta ();
if (host.IsTransientError (except.ErrorCode ()))
{
throw;
}
ThrowBadFormat ();
}
}
/*****************************************************************************/
void dng_xmp_sdk::AppendArrayItem (const char *ns,
const char *arrayName,
const char *itemValue,
bool isBag,
bool propIsStruct)
{
NeedMeta();
try
{
fPrivate->fMeta->AppendArrayItem (ns,
arrayName,
isBag ? kXMP_PropValueIsArray
: kXMP_PropArrayIsOrdered,
itemValue,
propIsStruct ? kXMP_PropValueIsStruct
: 0);
}
CATCH_XMP ("AppendArrayItem", true )
}
/*****************************************************************************/
int32 dng_xmp_sdk::CountArrayItems (const char *ns,
const char *path) const
{
if (HasMeta ())
{
try
{
return fPrivate->fMeta->CountArrayItems (ns, path);
}
CATCH_XMP ("CountArrayItems", false)
}
return 0;
}
/*****************************************************************************/
bool dng_xmp_sdk::Exists (const char *ns,
const char *path) const
{
if (HasMeta ())
{
try
{
return fPrivate->fMeta->DoesPropertyExist (ns, path);
}
catch (...)
{
// Does not exist...
}
}
return false;
}
/*****************************************************************************/
bool dng_xmp_sdk::HasNameSpace (const char *ns) const
{
bool result = false;
if (HasMeta ())
{
try
{
SXMPIterator iter (*fPrivate->fMeta, ns);
TXMP_STRING_TYPE nsTemp;
TXMP_STRING_TYPE prop;
if (iter.Next (&nsTemp,
&prop,
NULL,
NULL))
{
result = true;
}
}
CATCH_XMP ("HasNameSpace", true)
}
return result;
}
/*****************************************************************************/
void dng_xmp_sdk::Remove (const char *ns,
const char *path)
{
if (HasMeta ())
{
try
{
fPrivate->fMeta->DeleteProperty (ns, path);
}
CATCH_XMP ("DeleteProperty", false)
}
}
/*****************************************************************************/
void dng_xmp_sdk::RemoveProperties (const char *ns)
{
if (HasMeta ())
{
try
{
SXMPUtils::RemoveProperties (fPrivate->fMeta,
ns,
NULL,
kXMPUtil_DoAllProperties);
}
catch (...)
{
}
}
}
/*****************************************************************************/
bool dng_xmp_sdk::IsEmptyString (const char *ns,
const char *path)
{
if (HasMeta ())
{
try
{
TXMP_STRING_TYPE ss;
XMP_OptionBits options = 0;
if (fPrivate->fMeta->GetProperty (ns,
path,
&ss,
&options))
{
// Item must be simple.
if (XMP_PropIsSimple (options))
{
// Check for null strings.
return (ss.c_str () == 0 ||
ss.c_str () [0] == 0);
}
}
}
CATCH_XMP ("IsEmptyString", false)
}
return false;
}
/*****************************************************************************/
bool dng_xmp_sdk::IsEmptyArray (const char *ns,
const char *path)
{
if (HasMeta ())
{
try
{
TXMP_STRING_TYPE ss;
XMP_OptionBits options = 0;
if (fPrivate->fMeta->GetProperty (ns,
path,
&ss,
&options))
{
if (XMP_PropIsArray (options))
{
if (fPrivate->fMeta->GetArrayItem (ns,
path,
1,
&ss,
&options))
{
// If the first item is a null string...
if (XMP_PropIsSimple (options))
{
if ((ss.c_str () == 0 ||
ss.c_str () [0] == 0))
{
// And there is no second item.
if (!fPrivate->fMeta->GetArrayItem (ns,
path,
2,
&ss,
&options))
{
// Then we have an empty array.
return true;
}
}
}
}
else
{
// Unable to get first item, so array is empty.
return true;
}
}
}
}
CATCH_XMP ("IsEmptyArray", false)
}
return false;
}
/*****************************************************************************/
void dng_xmp_sdk::ComposeArrayItemPath (const char *ns,
const char *arrayName,
int32 index,
dng_string &s) const
{
try
{
std::string ss;
SXMPUtils::ComposeArrayItemPath (ns, arrayName, index, &ss);
s.Set (ss.c_str ());
return;
}
CATCH_XMP ("ComposeArrayItemPath", true)
}
/*****************************************************************************/
void dng_xmp_sdk::ComposeStructFieldPath (const char *ns,
const char *structName,
const char *fieldNS,
const char *fieldName,
dng_string &s) const
{
try
{
std::string ss;
SXMPUtils::ComposeStructFieldPath (ns,
structName,
fieldNS,
fieldName,
&ss);
s.Set (ss.c_str ());
return;
}
CATCH_XMP ("ComposeStructFieldPath", true)
}
/*****************************************************************************/
bool dng_xmp_sdk::GetNamespacePrefix (const char *uri,
dng_string &s) const
{
bool result = false;
if (HasMeta ())
{
try
{
std::string ss;
fPrivate->fMeta->GetNamespacePrefix (uri, &ss);
s.Set (ss.c_str ());
result = true;
}
CATCH_XMP ("GetNamespacePrefix", false)
}
return result;
}
/*****************************************************************************/
bool dng_xmp_sdk::GetString (const char *ns,
const char *path,
dng_string &s) const
{
bool result = false;
if (HasMeta ())
{
try
{
TXMP_STRING_TYPE ss;
if (fPrivate->fMeta->GetProperty (ns, path, &ss, NULL))
{
s.Set (ss.c_str ());
result = true;
}
}
CATCH_XMP ("GetProperty", false)
}
return result;
}
/*****************************************************************************/
void dng_xmp_sdk::ValidateStringList (const char *ns,
const char *path)
{
if (Exists (ns, path))
{
bool bogus = true;
try
{
XMP_Index index = 1;
TXMP_STRING_TYPE ss;
while (fPrivate->fMeta->GetArrayItem (ns,
path,
index++,
&ss,
NULL))
{
}
bogus = false;
}
CATCH_XMP ("GetArrayItem", false)
if (bogus)
{
Remove (ns, path);
}
}
}
/*****************************************************************************/
bool dng_xmp_sdk::GetStringList (const char *ns,
const char *path,
dng_string_list &list) const
{
bool result = false;
if (HasMeta ())
{
try
{
XMP_Index index = 1;
TXMP_STRING_TYPE ss;
while (fPrivate->fMeta->GetArrayItem (ns,
path,
index++,
&ss,
NULL))
{
dng_string s;
s.Set (ss.c_str ());
list.Append (s);
result = true;
}
}
CATCH_XMP ("GetArrayItem", false)
}
return result;
}
/*****************************************************************************/
bool dng_xmp_sdk::GetAltLangDefault (const char *ns,
const char *path,
dng_string &s) const
{
bool result = false;
if (HasMeta ())
{
try
{
TXMP_STRING_TYPE ss;
if (fPrivate->fMeta->GetLocalizedText (ns,
path,
"x-default",
"x-default",
NULL,
&ss,
NULL))
{
s.Set (ss.c_str ());
result = true;
}
//
// Special Case: treat the following two representation equivalently.
// The first is an empty alt lang array; the second is an array with
// an empty item. It seems that xmp lib could be generating both under
// some circumstances!
//
// <dc:description>
// <rdf:Alt/>
// </dc:description>
//
// and
//
// <dc:description>
// <rdf:Alt>
// <rdf:li xml:lang="x-default"/>
// </rdf:Alt>
// </dc:description>
//
else if (fPrivate->fMeta->GetProperty (ns,
path,
&ss,
NULL))
{
if (ss.empty ())
{
s.Clear ();
result = true;
}
}
}
CATCH_XMP ("GetLocalizedText", false)
}
return result;
}
/*****************************************************************************/
bool dng_xmp_sdk::GetStructField (const char *ns,
const char *path,
const char *fieldNS,
const char *fieldName,
dng_string &s) const
{
bool result = false;
if (HasMeta ())
{
try
{
TXMP_STRING_TYPE ss;
if (fPrivate->fMeta->GetStructField (ns,
path,
fieldNS,
fieldName,
&ss,
NULL))
{
s.Set (ss.c_str ());
result = true;
}
}
CATCH_XMP ("GetStructField", false)
}
return result;
}
/*****************************************************************************/
void dng_xmp_sdk::Set (const char *ns,
const char *path,
const char *text)
{
NeedMeta ();
try
{
fPrivate->fMeta->SetProperty (ns, path, text);
return;
}
catch (...)
{
// Failed for some reason.
}
// Remove existing value and try again.
Remove (ns, path);
try
{
fPrivate->fMeta->SetProperty (ns, path, text);
}
CATCH_XMP ("SetProperty", true)
}
/*****************************************************************************/
void dng_xmp_sdk::SetString (const char *ns,
const char *path,
const dng_string &s)
{
dng_string ss (s);
ss.SetLineEndings ('\n');
ss.StripLowASCII ();
Set (ns, path, ss.Get ());
}
/*****************************************************************************/
void dng_xmp_sdk::SetStringList (const char *ns,
const char *path,
const dng_string_list &list,
bool isBag)
{
// Remove any existing structure.
Remove (ns, path);
// If list is not empty, add the items.
if (list.Count ())
{
NeedMeta ();
for (uint32 index = 0; index < list.Count (); index++)
{
dng_string s (list [index]);
s.SetLineEndings ('\n');
s.StripLowASCII ();
try
{
fPrivate->fMeta->AppendArrayItem (ns,
path,
isBag ? kXMP_PropValueIsArray
: kXMP_PropArrayIsOrdered,
s.Get ());
}
CATCH_XMP ("AppendArrayItem", true)
}
}
}
/*****************************************************************************/
void dng_xmp_sdk::SetAltLangDefault (const char *ns,
const char *path,
const dng_string &s)
{
NeedMeta ();
Remove (ns, path);
dng_string ss (s);
ss.SetLineEndings ('\n');
ss.StripLowASCII ();
try
{
fPrivate->fMeta->SetLocalizedText (ns,
path,
"x-default",
"x-default",
ss.Get ());
}
CATCH_XMP ("SetLocalizedText", true)
}
/*****************************************************************************/
void dng_xmp_sdk::SetStructField (const char *ns,
const char *path,
const char *fieldNS,
const char *fieldName,
const char *text)
{
NeedMeta ();
try
{
fPrivate->fMeta->SetStructField (ns,
path,
fieldNS,
fieldName,
text);
}
CATCH_XMP ("SetStructField", true)
}
/*****************************************************************************/
void dng_xmp_sdk::DeleteStructField (const char *ns,
const char *structName,
const char *fieldNS,
const char *fieldName)
{
if (HasMeta ())
{
try
{
fPrivate->fMeta->DeleteStructField (ns, structName, fieldNS, fieldName);
}
catch (...)
{
}
}
}
/*****************************************************************************/
dng_memory_block * dng_xmp_sdk::Serialize (dng_memory_allocator &allocator,
bool asPacket,
uint32 targetBytes,
uint32 padBytes,
bool forJPEG,
bool compact) const
{
// The largest XMP packet you can embed in JPEG using normal methods:
const uint32 kJPEG_XMP_Limit = 65504;
if (HasMeta ())
{
TXMP_STRING_TYPE s;
bool havePacket = false;
// Note that the XMP lib is changing its default to compact format
// in the future, so the following line will need to change.
uint32 formatOption = compact ? kXMP_UseCompactFormat : 0;
if (asPacket && targetBytes)
{
try
{
fPrivate->fMeta->SerializeToBuffer (&s,
formatOption | kXMP_ExactPacketLength,
targetBytes,
"",
" ");
havePacket = true;
}
catch (...)
{
// Most likely the packet cannot fit in the target
// byte count. So try again without the limit.
}
}
if (!havePacket)
{
try
{
fPrivate->fMeta->SerializeToBuffer (&s,
formatOption |
(asPacket ? 0
: kXMP_OmitPacketWrapper),
(asPacket ? padBytes
: 0),
"",
" ");
}
CATCH_XMP ("SerializeToBuffer", true)
}
uint32 packetLen = (uint32) s.size ();
if (forJPEG && asPacket && padBytes > 0 && targetBytes <= kJPEG_XMP_Limit &&
packetLen > kJPEG_XMP_Limit)
{
uint32 overLimitCount = packetLen - kJPEG_XMP_Limit;
if (overLimitCount > padBytes)
{
padBytes = 0;
}
else
{
padBytes -= overLimitCount;
}
try
{
fPrivate->fMeta->SerializeToBuffer (&s,
formatOption,
padBytes,
"",
" ");
}
CATCH_XMP ("SerializeToBuffer", true)
packetLen = (uint32) s.size ();
}
if (packetLen)
{
AutoPtr<dng_memory_block> buffer (allocator.Allocate (packetLen));
memcpy (buffer->Buffer (), s.c_str (), packetLen);
return buffer.Release ();
}
}
return NULL;
}
/*****************************************************************************/
void dng_xmp_sdk::PackageForJPEG (dng_memory_allocator &allocator,
AutoPtr<dng_memory_block> &stdBlock,
AutoPtr<dng_memory_block> &extBlock,
dng_string &extDigest) const
{
if (HasMeta ())
{
TXMP_STRING_TYPE stdStr;
TXMP_STRING_TYPE extStr;
TXMP_STRING_TYPE digestStr;
try
{
SXMPUtils::PackageForJPEG (*fPrivate->fMeta,
&stdStr,
&extStr,
&digestStr);
}
CATCH_XMP ("PackageForJPEG", true)
uint32 stdLen = (uint32) stdStr.size ();
uint32 extLen = (uint32) extStr.size ();
if (stdLen)
{
stdBlock.Reset (allocator.Allocate (stdLen));
memcpy (stdBlock->Buffer (), stdStr.c_str (), stdLen);
}
if (extLen)
{
extBlock.Reset (allocator.Allocate (extLen));
memcpy (extBlock->Buffer (), extStr.c_str (), extLen);
if (digestStr.size () != 32)
{
ThrowProgramError ();
}
extDigest.Set (digestStr.c_str ());
}
}
}
/*****************************************************************************/
void dng_xmp_sdk::MergeFromJPEG (const dng_xmp_sdk *xmp)
{
if (xmp && xmp->HasMeta ())
{
NeedMeta ();
try
{
SXMPUtils::MergeFromJPEG (fPrivate->fMeta,
*xmp->fPrivate->fMeta);
}
CATCH_XMP ("MergeFromJPEG", true)
}
}
/*****************************************************************************/
void dng_xmp_sdk::ReplaceXMP (dng_xmp_sdk *xmp)
{
ClearMeta ();
if (xmp && xmp->HasMeta ())
{
fPrivate->fMeta = xmp->fPrivate->fMeta;
xmp->fPrivate->fMeta = NULL;
}
}
/*****************************************************************************/
bool dng_xmp_sdk::IteratePaths (IteratePathsCallback *callback,
void *callbackData,
const char* startingNS,
const char* startingPath)
{
if (HasMeta ())
{
try
{
SXMPIterator iter (*fPrivate->fMeta, startingNS, startingPath);
TXMP_STRING_TYPE ns;
TXMP_STRING_TYPE prop;
while (iter.Next (&ns,
&prop,
NULL,
NULL))
{
if (!callback (ns .c_str (),
prop.c_str (),
callbackData))
{
return false;
}
}
}
CATCH_XMP ("IteratePaths", true)
}
return true;
}
/*****************************************************************************/
#if qDNGXMPDocOps
/*****************************************************************************/
void dng_xmp_sdk::DocOpsOpenXMP (const char *srcMIMI)
{
if (srcMIMI [0])
{
NeedMeta ();
try
{
SXMPDocOps docOps;
docOps.OpenXMP (fPrivate->fMeta,
srcMIMI);
}
CATCH_XMP ("DocOpsOpenXMP", false)
Set (XMP_NS_DC,
"format",
srcMIMI);
}
}
/*****************************************************************************/
void dng_xmp_sdk::DocOpsPrepareForSave (const char *srcMIMI,
const char *dstMIMI,
bool newPath)
{
NeedMeta ();
try
{
SXMPDocOps docOps;
docOps.OpenXMP (fPrivate->fMeta,
srcMIMI,
"old path");
docOps.NoteChange (kXMP_Part_All);
docOps.PrepareForSave (dstMIMI,
newPath ? "new path" : "old path");
}
CATCH_XMP ("DocOpsPrepareForSave", false)
Set (XMP_NS_DC,
"format",
dstMIMI);
}
/*****************************************************************************/
void dng_xmp_sdk::DocOpsUpdateMetadata (const char *srcMIMI)
{
NeedMeta ();
try
{
SXMPDocOps docOps;
docOps.OpenXMP (fPrivate->fMeta,
srcMIMI);
docOps.NoteChange (kXMP_Part_Metadata);
docOps.PrepareForSave (srcMIMI);
}
CATCH_XMP ("DocOpsUpdateMetadata", false)
}
/*****************************************************************************/
#endif
/*****************************************************************************/