/*****************************************************************************/ // 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 #include #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! // // // // // // and // // // // // // // 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 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 &stdBlock, AutoPtr &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 /*****************************************************************************/