diff --git a/README-XMP b/README-XMP new file mode 100644 index 00000000..9c0d237e --- /dev/null +++ b/README-XMP @@ -0,0 +1,77 @@ +XMP support +=========== +Exiv2 uses the Adobe XMP toolkit (XMP SDK) to parse +and serialize XMP packets (only the XMPCore component). + +Todo: XMP support is controlled with the ENABLE_XMP directive +in config/config.mk.in. This will be a configure option +eventually. + +Top-level classes to access XMP metadata are XmpData, +XmpDatum and XmpKey. They work similar to the corresponding +Exif and IPTC classes. The property-repository is XmpProperties. +In addition to the expected new members, class Image also +has a new interface to access the raw XMP packet. + +Supported XMP types +------------------- +Simple types : supported +Structures : not supported +Arrays : unordered and ordered arrays are supported + alternative arrays are not supported + +Property Qualifiers : not supported +Language Alternatives : not supported + +XMP properties are accessed through keys of the form +"Xmp..", where is the preferred +(or rather, registered) prefix for a schema namespace and + is the name of a property in that namespace. Only +known properties in known namespaces are supported at the +moment. Functions to register namespaces and properterties +are not provided yet. + +Note: Unlike Exif and IPTC tags, XMP properties do not have +a tag number. + +XMP toolkit installation +======================== +This is what worked for me on a Debian GNU/Linux (testing) +system. Your mileage may vary. Please check with Adobe if +you encounter problems with the XMP toolkit installation. + +Installation directory +---------------------- +The top-level exiv2 directory ($HOME/src/exiv2/ on my system). + +If you install XMP elsewhere, or use different OS or STAGE +options to the make command below, you will need to manually +modify the exiv2/config/config.mk.in (before running the +configure script). There is no autoconf-magic yet to set +this via configure options. + +External packages (non-Debian) +----------------- +xmp_v411_sdk.zip - from adobe.com: http://www.adobe.com/devnet/xmp/sdk/eula.html +expat-2.0.1.tar.gz - from sourceforge.net: http://sourceforge.net/project/showfiles.php?group_id=10127 +exiv2 - from SVN + +Installation steps +------------------ +cd $HOME/src/exiv2/ +cp /path/to/xmp_v411_sdk.zip ./ +unzip xmp_v411_sdk.zip + +cp /path/to/expat-2.0.1.tar.gz XMP-SDK/third-party/ +cd XMP-SDK/third-party/ +tar zxvf expat-2.0.1.tar.gz +mv expat expat-orig +mv expat-2.0.1 expat + +cd ../build/gcc +make -f XMPCore.mak OS=i80386linux STAGE=debug + +Expected result +--------------- +Now there should be a library +../../public/libraries/i80386linux/debug/libXMPCoreStaticDebug.a diff --git a/config/config.mk.in b/config/config.mk.in index 96d2eff9..4771b611 100644 --- a/config/config.mk.in +++ b/config/config.mk.in @@ -46,7 +46,7 @@ GXX = @GXX@ # Common compiler flags (warnings, symbols [-ggdb], optimization [-O2], etc) CXXFLAGS = @CXXFLAGS@ ifeq ($(GXX),yes) - CXXFLAGS += -pedantic -Wall -Wundef -Wcast-align -Wconversion -Wpointer-arith -Wformat-security -Wmissing-format-attribute -W + CXXFLAGS += -Wall -Wcast-align -Wconversion -Wpointer-arith -Wformat-security -Wmissing-format-attribute -W endif # Command to run only the preprocessor @@ -75,6 +75,20 @@ ifeq ($(GCC),yes) CFLAGS += -Wall endif +# ********************************************************************** +# XMP support +ENABLE_XMP = 1 + +ifdef ENABLE_XMP + CPPFLAGS += -DEXV_HAVE_XMP_TOOLKIT -DUNIX_ENV -I$(top_srcdir)/XMP-SDK/public/include + LDFLAGS += -L$(top_srcdir)/XMP-SDK/public/libraries/i80386linux/debug -lXMPCoreStaticDebug +else + # XMP Toolkit doesn't compile cleanly with these + ifeq ($(GXX),yes) + CXXFLAGS += -Wundef -pedantic + endif +endif + # ********************************************************************** # Libraries and Functions HAVE_LIBZ = @HAVE_LIBZ@ diff --git a/config/configure.ac b/config/configure.ac index 8a9355f7..8b335bab 100644 --- a/config/configure.ac +++ b/config/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT(exiv2, 0.15, ahuggel@gmx.net) +AC_INIT(exiv2, 0.16, ahuggel@gmx.net) # See http://www.gnu.org/software/libtool/manual.html#Updating-version-info EXIV2_LTVERSION=1:0:1 PACKAGE=$PACKAGE_NAME diff --git a/src/Makefile b/src/Makefile index a9c4a37b..bb2d3fee 100644 --- a/src/Makefile +++ b/src/Makefile @@ -92,6 +92,10 @@ CCSRC += rafimage.cpp \ types.cpp \ value.cpp \ version.cpp +ifdef ENABLE_XMP +CCSRC += properties.cpp \ + xmp.cpp +endif # Add library C source files to this list ifndef HAVE_TIMEGM @@ -118,7 +122,8 @@ BINSRC = addmoddel.cpp \ write-test.cpp \ write2-test.cpp \ tiffparse.cpp \ - xmpdump.cpp + xmpparse.cpp \ + xmpparser-test.cpp # Main source file of the Exiv2 application EXIV2MAIN = exiv2.cpp diff --git a/src/actions.cpp b/src/actions.cpp index c2a31ab7..76ba0dea 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -49,6 +49,7 @@ EXIV2_RCSID("@(#) $Id$") #include "exif.hpp" #include "canonmn.hpp" #include "iptc.hpp" +#include "xmp.hpp" #include "futils.hpp" #include "i18n.h" // NLS support. @@ -128,6 +129,23 @@ namespace { @return 0 if successful, -1 if the file was skipped, 1 on error. */ int renameFile(std::string& path, const struct tm* tm); + + /*! + @brief Make a file path from the current file path, destination + directory (if any) and the filename extension passed in. + @param path Path of the existing file + @param ext New filename extension (incl. the dot '.' if required) + @return 0 if successful, 1 if the new file exists and the user + chose not to overwrite it. + */ + std::string newFilePath(const std::string& path, const std::string& ext); + + /*! + @brief Check if file \em path exists and whether it should be + overwritten. Ask user if necessary. Return 1 if the file + exists and shouldn't be overwritten, else 0. + */ + int dontOverwrite(const std::string& path); } // ***************************************************************************** @@ -201,6 +219,7 @@ namespace Action { case Params::pmSummary: rc = printSummary(); break; case Params::pmList: rc = printList(); break; case Params::pmIptc: rc = printIptc(); break; + case Params::pmXmp: rc = printXmp(); break; case Params::pmComment: rc = printComment(); break; } return rc; @@ -700,7 +719,7 @@ namespace Action { Exiv2::IptcData& iptcData = image->iptcData(); if (iptcData.empty()) { std::cerr << path_ - << ": " << _("No Iptc data found in the file\n"); + << ": " << _("No IPTC data found in the file\n"); return -3; } Exiv2::IptcData::const_iterator end = iptcData.end(); @@ -725,6 +744,44 @@ namespace Action { return 0; } // Print::printIptc + int Print::printXmp() + { + if (!Exiv2::fileExists(path_, true)) { + std::cerr << path_ + << ": " << _("Failed to open the file\n"); + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path_); + assert(image.get() != 0); + image->readMetadata(); + Exiv2::XmpData& xmpData = image->xmpData(); + if (xmpData.empty()) { + std::cerr << path_ + << ": " << _("No XMP data found in the file\n"); + return -3; + } + Exiv2::XmpData::const_iterator end = xmpData.end(); + Exiv2::XmpData::const_iterator md; + bool manyFiles = Params::instance().files_.size() > 1; + for (md = xmpData.begin(); md != end; ++md) { + std::cout << std::setfill(' ') << std::left; + if (manyFiles) { + std::cout << std::setw(20) << path_ << " "; + } + std::cout << std::setw(44) + << md->key() << " " + << std::setw(9) << std::setfill(' ') << std::left + << md->typeName() << " " + << std::dec << std::setw(3) + << std::setfill(' ') << std::right + << md->count() << " " + << std::dec << md->value() + << std::endl; + } + + return 0; + } // Print::printXmp + int Print::printComment() { if (!Exiv2::fileExists(path_, true)) { @@ -736,7 +793,7 @@ namespace Action { assert(image.get() != 0); image->readMetadata(); if (Params::instance().verbose_) { - std::cout << _("Jpeg comment") << ": "; + std::cout << _("JPEG comment") << ": "; } std::cout << image->comment() << std::endl; return 0; @@ -867,6 +924,12 @@ namespace Action { if (0 == rc && Params::instance().target_ & Params::ctComment) { rc = eraseComment(image.get()); } + if (0 == rc && Params::instance().target_ & Params::ctXmp) { + // Todo: Implement me! + if (!image->xmpData().empty()) { + std::cerr << "Deletion of XMP data not implemented yet.\n"; + } + } if (0 == rc) { image->writeMetadata(); } @@ -910,7 +973,7 @@ namespace Action { int Erase::eraseIptcData(Exiv2::Image* image) const { if (Params::instance().verbose_ && image->iptcData().count() > 0) { - std::cout << _("Erasing Iptc data from the file") << std::endl; + std::cout << _("Erasing IPTC data from the file") << std::endl; } image->clearIptcData(); return 0; @@ -919,7 +982,7 @@ namespace Action { int Erase::eraseComment(Exiv2::Image* image) const { if (Params::instance().verbose_ && image->comment().size() > 0) { - std::cout << _("Erasing Jpeg comment from the file") << std::endl; + std::cout << _("Erasing JPEG comment from the file") << std::endl; } image->clearComment(); return 0; @@ -942,18 +1005,12 @@ namespace Action { if (Params::instance().target_ & Params::ctThumb) { rc = writeThumbnail(); } - if (Params::instance().target_ & ~Params::ctThumb) { - std::string directory = Params::instance().directory_; - if (directory.empty()) directory = Util::dirname(path_); - std::string exvPath = directory + EXV_SEPERATOR_STR - + Util::basename(path_, true) + ".exv"; - if (!Params::instance().force_ && Exiv2::fileExists(exvPath)) { - std::cout << Params::instance().progname() - << ": " << _("Overwrite") << " `" << exvPath << "'? "; - std::string s; - std::cin >> s; - if (s[0] != 'y' && s[0] != 'Y') return 0; - } + if (Params::instance().target_ & Params::ctXmpPacket) { + rc = writeXmp(); + } + if (Params::instance().target_ & ~Params::ctThumb & ~Params::ctXmpPacket) { + std::string exvPath = newFilePath(path_, ".exv"); + if (dontOverwrite(exvPath)) return 0; rc = metacopy(path_, exvPath, false); } return rc; @@ -965,6 +1022,37 @@ namespace Action { return 1; } // Extract::run + int Extract::writeXmp() const + { + if (!Exiv2::fileExists(path_, true)) { + std::cerr << path_ + << ": " << _("Failed to open the file\n"); + return -1; + } + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path_); + assert(image.get() != 0); + image->readMetadata(); + const std::string& xmpPacket = image->xmpPacket(); + if (xmpPacket.empty()) { + return -3; + } + std::string xmpPath = newFilePath(path_, ".xmp"); + if (dontOverwrite(xmpPath)) return 0; + if (Params::instance().verbose_) { + std::cout << _("Writing XMP packet from") << " " << path_ + << " " << _("to") << " " << xmpPath << std::endl; + } + std::ofstream file(xmpPath.c_str()); + if (!file) { + std::cerr << Params::instance().progname() << ": " + << _("Failed to open file ") << " " << xmpPath << ": " + << Exiv2::strError() << "\n"; + return 1; + } + file << xmpPacket; + return 0; + } // Extract::writeXmp + int Extract::writeThumbnail() const { if (!Exiv2::fileExists(path_, true)) { @@ -981,34 +1069,25 @@ namespace Action { << ": " << _("No Exif data found in the file\n"); return -3; } - - std::string directory = Params::instance().directory_; - if (directory.empty()) directory = Util::dirname(path_); - std::string thumb = directory + EXV_SEPERATOR_STR - + Util::basename(path_, true) + "-thumb"; - std::string thumbExt = exifData.thumbnailExtension(); int rc = 0; + std::string thumbExt = exifData.thumbnailExtension(); if (thumbExt.empty()) { std::cerr << path_ << ": " << _("Image does not contain an Exif thumbnail\n"); } else { + std::string thumb = newFilePath(path_, "-thumb"); + std::string thumbPath = thumb + thumbExt; + if (dontOverwrite(thumbPath)) return 0; if (Params::instance().verbose_) { Exiv2::DataBuf buf = exifData.copyThumbnail(); std::cout << _("Writing") << " " << exifData.thumbnailFormat() << " " << _("thumbnail") << " (" << buf.size_ << " " << _("Bytes") << ") " << _("to file") << " " - << thumb << thumbExt << std::endl; - } - if (!Params::instance().force_ && Exiv2::fileExists(thumb + thumbExt)) { - std::cout << Params::instance().progname() - << ": " << _("Overwrite") << " `" << thumb + thumbExt << "'? "; - std::string s; - std::cin >> s; - if (s[0] != 'y' && s[0] != 'Y') return 0; + << thumbPath << std::endl; } rc = exifData.writeThumbnail(thumb); if (rc) { - std::cerr << thumb << ": " << _("Exif data doesn't contain a thumbnail\n"); + std::cerr << path_ << ": " << _("Exif data doesn't contain a thumbnail\n"); } } return rc; @@ -1042,15 +1121,17 @@ namespace Action { if ( rc == 0 && Params::instance().target_ & Params::ctExif || Params::instance().target_ & Params::ctIptc - || Params::instance().target_ & Params::ctComment) { - std::string directory = Params::instance().directory_; - if (directory.empty()) directory = Util::dirname(path); + || Params::instance().target_ & Params::ctComment + || Params::instance().target_ & Params::ctXmp) { std::string suffix = Params::instance().suffix_; if (suffix.empty()) suffix = ".exv"; - std::string exvPath = directory + EXV_SEPERATOR_STR - + Util::basename(path, true) + suffix; + std::string exvPath = newFilePath(path, suffix); rc = metacopy(exvPath, path, true); } + if (0 == rc && Params::instance().target_ & Params::ctXmpPacket) { + // Todo: Implement me! + std::cerr << "Insertion of XMP packet from *.xmp file not implemented yet.\n"; + } if (Params::instance().preserve_) { ts.touch(path); } @@ -1065,10 +1146,7 @@ namespace Action { int Insert::insertThumbnail(const std::string& path) const { - std::string directory = Params::instance().directory_; - if (directory.empty()) directory = Util::dirname(path); - std::string thumbPath = directory + EXV_SEPERATOR_STR - + Util::basename(path, true) + "-thumb.jpg"; + std::string thumbPath = newFilePath(path, "-thumb.jpg"); if (!Exiv2::fileExists(thumbPath, true)) { std::cerr << thumbPath << ": " << _("Failed to open the file\n"); @@ -1137,7 +1215,7 @@ namespace Action { { if (!Params::instance().jpegComment_.empty()) { if (Params::instance().verbose_) { - std::cout << _("Setting Jpeg comment") << " '" + std::cout << _("Setting JPEG comment") << " '" << Params::instance().jpegComment_ << "'" << std::endl; @@ -1238,6 +1316,13 @@ namespace Action { } } } + else { + std::cerr << _("Warning") << ": " << modifyCmd.key_ << ": " + << _("Failed to read") << " " + << Exiv2::TypeInfo::typeName(value->typeId()) + << " " << _("value") + << " \"" << modifyCmd.value_ << "\"\n"; + } } void Modify::delMetadatum(Exiv2::Image* pImage, const ModifyCmd& modifyCmd) @@ -1550,15 +1635,24 @@ namespace { if ( Params::instance().target_ & Params::ctIptc && !sourceImage->iptcData().empty()) { if (Params::instance().verbose_) { - std::cout << _("Writing Iptc data from") << " " << source + std::cout << _("Writing IPTC data from") << " " << source << " " << _("to") << " " << target << std::endl; } targetImage->setIptcData(sourceImage->iptcData()); } + if ( Params::instance().target_ & Params::ctXmp + && !sourceImage->xmpData().empty()) { + if (Params::instance().verbose_) { + std::cout << _("Writing XMP data from") << " " << source + << " " << _("to") << " " << target << std::endl; + } + // Todo: Should use XMP packet if there are no XMP modification commands + targetImage->setXmpData(sourceImage->xmpData()); + } if ( Params::instance().target_ & Params::ctComment && !sourceImage->comment().empty()) { if (Params::instance().verbose_) { - std::cout << _("Writing Jpeg comment from") << " " << source + std::cout << _("Writing JPEG comment from") << " " << source << " " << _("to") << " " << target << std::endl; } targetImage->setComment(sourceImage->comment()); @@ -1668,4 +1762,25 @@ namespace { return 0; } // renameFile + std::string newFilePath(const std::string& path, const std::string& ext) + { + std::string directory = Params::instance().directory_; + if (directory.empty()) directory = Util::dirname(path); + std::string newPath = directory + EXV_SEPERATOR_STR + + Util::basename(path, true) + ext; + return newPath; + } + + int dontOverwrite(const std::string& path) + { + if (!Params::instance().force_ && Exiv2::fileExists(path)) { + std::cout << Params::instance().progname() + << ": " << _("Overwrite") << " `" << path << "'? "; + std::string s; + std::cin >> s; + if (s[0] != 'y' && s[0] != 'Y') return 1; + } + return 0; + } + } diff --git a/src/actions.hpp b/src/actions.hpp index 2316face..ead9eae0 100644 --- a/src/actions.hpp +++ b/src/actions.hpp @@ -164,6 +164,8 @@ namespace Action { int printComment(); //! Print uninterpreted Iptc information int printIptc(); + //! print uninterpreted XMP information + int printXmp(); //! Print Exif summary information int printSummary(); //! Print the list of Exif data in user defined format @@ -268,6 +270,8 @@ namespace Action { on the format of the Exif thumbnail image. */ int writeThumbnail() const; + //! Write the XMP packet to a file. + int writeXmp() const; private: virtual Extract* clone_() const; diff --git a/src/basicio.cpp b/src/basicio.cpp index 36716faa..ea55b1bd 100644 --- a/src/basicio.cpp +++ b/src/basicio.cpp @@ -592,4 +592,25 @@ namespace Exiv2 { return "MemIo"; } + // ************************************************************************* + // free functions + + DataBuf readFile(const std::string& path) + { + FileIo file(path); + if (file.open("rb") != 0) { + throw Error(10, path, "rb", strError()); + } + struct stat st; + if (0 != stat(path.c_str(), &st)) { + throw Error(2, path, strError(), "stat"); + } + DataBuf buf(st.st_size); + long len = file.read(buf.pData_, buf.size_); + if (len != buf.size_) { + throw Error(2, path, strError(), "FileIo::read"); + } + return buf; + } + } // namespace Exiv2 diff --git a/src/basicio.hpp b/src/basicio.hpp index d2313c78..60f8ae27 100644 --- a/src/basicio.hpp +++ b/src/basicio.hpp @@ -676,6 +676,13 @@ namespace Exiv2 { // METHODS void reserve(long wcount); }; // class MemIo + +// ***************************************************************************** +// template, inline and free functions + + //! Read file \em path into a DataBuf, which is returned. + DataBuf readFile(const std::string& path); + } // namespace Exiv2 #endif // #ifndef BASICIO_HPP_ diff --git a/src/error.cpp b/src/error.cpp index 6c6d900b..841bcd26 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -77,10 +77,11 @@ namespace Exiv2 { ErrMsg( 32, N_("Setting %1 in %2 images is not supported")), // %1=metadata type, %2=image format ErrMsg( 33, N_("This does not look like a CRW image")), ErrMsg( 34, N_("%1: Not supported")), // %1=function - // 35 - // 36 + ErrMsg( 35, N_("Unknown XMP prefix `%1'")), // %1=prefix + ErrMsg( 36, N_("No XMP property list for prefix `%1'")), // %1=prefix ErrMsg( 37, N_("Size of %1 JPEG segment is larger than 65535 bytes")), // %1=type of metadata (Exif, IPTC, JPEG comment) - + ErrMsg( 38, N_("Unknown XMP property `%1:%2'")), // %1=prefix, %2=property name + ErrMsg( 39, N_("XMP Toolkit error %1: %2")), // %1=XMP_Error::GetID(), %2=XMP_Error::GetErrMsg() // Last error message (message is not used) ErrMsg( -2, N_("(Unknown Error)")) }; diff --git a/src/exif.cpp b/src/exif.cpp index ed545215..c5d8ee90 100644 --- a/src/exif.cpp +++ b/src/exif.cpp @@ -82,9 +82,6 @@ namespace { uint32_t offset, Exiv2::ByteOrder byteOrder); - // Read file path into a DataBuf, which is returned. - Exiv2::DataBuf readFile(const std::string& path); - } // ***************************************************************************** @@ -1300,22 +1297,4 @@ namespace { pos->setValue(offset, byteOrder); } - Exiv2::DataBuf readFile(const std::string& path) - { - Exiv2::FileIo file(path); - if (file.open("rb") != 0) { - throw Exiv2::Error(10, path, "rb", Exiv2::strError()); - } - struct stat st; - if (0 != stat(path.c_str(), &st)) { - throw Exiv2::Error(2, path, Exiv2::strError(), "::stat"); - } - Exiv2::DataBuf buf(st.st_size); - long len = file.read(buf.pData_, buf.size_); - if (len != buf.size_) { - throw Exiv2::Error(2, path, Exiv2::strError(), "FileIo::read"); - } - return buf; - } - } diff --git a/src/exif.hpp b/src/exif.hpp index 5f6cc761..294b1e98 100644 --- a/src/exif.hpp +++ b/src/exif.hpp @@ -135,10 +135,6 @@ namespace Exiv2 { Calls setValue(const Value*). */ Exifdatum& operator=(const Value& value); - /*! - @brief Set the value. This method copies (clones) the value pointed - to by \em pValue. - */ void setValue(const Value* pValue); /*! @brief Set the value to the string \em value. Uses Value::read(const @@ -224,64 +220,16 @@ namespace Exiv2 { //! Return the value as a string. std::string toString() const { return value_.get() == 0 ? "" : value_->toString(); } - /*! - @brief Return the n-th component of the value converted to - long. The return value is -1 if the value of the Exifdatum is - not set and the behaviour of the method is undefined if there - is no n-th component. - */ + std::string toString(long n) const + { return value_.get() == 0 ? "" : value_->toString(n); } long toLong(long n =0) const { return value_.get() == 0 ? -1 : value_->toLong(n); } - /*! - @brief Return the n-th component of the value converted to - float. The return value is -1 if the value of the Exifdatum is - not set and the behaviour of the method is undefined if there - is no n-th component. - */ float toFloat(long n =0) const { return value_.get() == 0 ? -1 : value_->toFloat(n); } - /*! - @brief Return the n-th component of the value converted to - Rational. The return value is -1/1 if the value of the - Exifdatum is not set and the behaviour of the method is - undefined if there is no n-th component. - */ Rational toRational(long n =0) const { return value_.get() == 0 ? Rational(-1, 1) : value_->toRational(n); } - /*! - @brief Return an auto-pointer to a copy (clone) of the value. The - caller owns this copy and the auto-pointer ensures that it will - be deleted. - - This method is provided for users who need full control over the - value. A caller may, e.g., downcast the pointer to the appropriate - subclass of Value to make use of the interface of the subclass to set - or modify its contents. - - @return An auto-pointer to a copy (clone) of the value, 0 if the value - is not set. - */ Value::AutoPtr getValue() const { return value_.get() == 0 ? Value::AutoPtr(0) : value_->clone(); } - /*! - @brief Return a constant reference to the value. - - This method is provided mostly for convenient and versatile output of - the value which can (to some extent) be formatted through standard - stream manipulators. Do not attempt to write to the value through - this reference. - - Example:
- @code - ExifData::const_iterator i = exifData.findKey(key); - if (i != exifData.end()) { - std::cout << i->key() << " " << std::hex << i->value() << "\n"; - } - @endcode - - @return A constant reference to the value. - @throw Error if the value is not set. - */ const Value& value() const; //! Return the size of the data area. long sizeDataArea() const @@ -300,7 +248,6 @@ namespace Exiv2 { */ DataBuf dataArea() const { return value_.get() == 0 ? DataBuf(0, 0) : value_->dataArea(); } - //@} private: diff --git a/src/exiv2.1 b/src/exiv2.1 index cf22e176..ce8ef90b 100644 --- a/src/exiv2.1 +++ b/src/exiv2.1 @@ -3,7 +3,7 @@ .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) -.TH EXIV2 1 "June 3rd, 2007" +.TH EXIV2 1 "August 27th, 2007" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -17,7 +17,7 @@ .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME -exiv2 \- Exif/IPTC metadata manipulation tool +exiv2 \- Image metadata manipulation tool .SH SYNOPSIS .B exiv2 [\fIoptions\fP] [\fIaction\fP] \fIfile\fP ... @@ -28,11 +28,12 @@ exiv2 \- Exif/IPTC metadata manipulation tool .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. .B exiv2 -is a program to read and write Exif and IPTC image metadata and image -comments. Supported image formats are JPEG, Canon CRW and Canon THM. -Read-only support is currently available for PNG and TIFF format and -includes TIFF-based RAW formats such as Adobe DNG, Canon CR2, Fujifilm -RAF, Minolta MRW, Nikon NEF, Pentax PEF, Sony ARW and Sony SR2. +is a program to read and write Exif, IPTC and XMP image metadata and +image comments. Supported image formats are JPEG, Canon CRW and Canon +THM. Read-only support is currently available for PNG and TIFF format +and includes TIFF-based RAW formats such as Adobe DNG, Canon CR2, +Fujifilm RAF, Minolta MRW, Nikon NEF, Pentax PEF, Sony ARW and Sony +SR2. .SH ACTIONS The \fIaction\fP argument is only required if it is not clear from the \fIoptions\fP which action is implied. @@ -42,15 +43,15 @@ Print image metadata. This is the default action, i.e., the command \fIexiv2 image.jpg\fP will print a summary of the image Exif metadata. .TP .B ex | extract -Extract metadata to *.exv and thumbnail image files. Modification -commands can be applied on-the-fly. +Extract metadata to *.exv, *.xmp and thumbnail image files. +Modification commands can be applied on-the-fly. .TP .B in | insert -Insert metadata from corresponding *.exv files. Use option \fB\-S\fP -\fI.suf\fP to change the suffix of the input files. Since files of any -supported format can be used as input files, this command can be used -to copy the metadata between files of different formats. Modification -commands can be applied on-the-fly. +Insert metadata from corresponding *.exv, *.xmp and thumbnail files. +Use option \fB\-S\fP \fI.suf\fP to change the suffix of the input +files. Since files of any supported format can be used as input files, +this command can be used to copy the metadata between files of +different formats. Modification commands can be applied on-the-fly. .TP .B rm | delete Delete image metadata from the files. @@ -132,6 +133,8 @@ h : hexdump of the Exif data (shortcut for -Pxgnycsh) .br i : IPTC data values .br +x : XMP properties +.br c : JPEG comment .TP .B \-P \fIcols\fP @@ -171,17 +174,24 @@ t : Exif thumbnail only .br i : IPTC data .br +x : XMP packet +.br c : JPEG comment .TP .B \-i \fItgt\fP Insert target(s) for the 'insert' action. Possible targets are the -same as those for the \fB\-d\fP option. Only JPEG thumbnails can be -inserted (not TIFF thumbnails), they need to be named -\fIfile\fP\-thumb.jpg. +same as those for the \fB\-d\fP option, plus: +.br +X : Insert XMP packet from .xmp +.br +Only JPEG thumbnails can be inserted (not TIFF thumbnails), they need to +be named \fIfile\fP\-thumb.jpg. .TP .B \-e \fItgt\fP Extract target(s) for the 'extract' action. Possible targets are the same -as those for the \fB\-d\fP option. +as those for the \fB\-d\fP option, plus: +.br +X : Extract XMP packet to .xmp .TP .B \-r \fIfmt\fP Filename format for the 'rename' action. The format string follows @@ -349,7 +359,7 @@ Sample command file. Taglists with \fIkey\fP and default \fItype\fP values. .SH AUTHORS .B exiv2 -was written by Andreas HUGGEL . +was written by Andreas Huggel . .PP This manual page was originally written by KELEMEN Peter , for the Debian project. diff --git a/src/exiv2.cpp b/src/exiv2.cpp index d73d7565..eacc7390 100644 --- a/src/exiv2.cpp +++ b/src/exiv2.cpp @@ -214,12 +214,12 @@ void Params::help(std::ostream& os) const << _(" rm | delete Delete image metadata from the files.\n") << _(" in | insert Insert metadata from corresponding *.exv files.\n" " Use option -S to change the suffix of the input files.\n") - << _(" ex | extract Extract metadata to *.exv and thumbnail image files.\n") + << _(" ex | extract Extract metadata to *.exv, *.xmp and thumbnail image files.\n") << _(" mv | rename Rename files and/or set file timestamps according to the\n" " Exif create timestamp. The filename format can be set with\n" " -r format, timestamp options are controlled with -t and -T.\n") << _(" mo | modify Apply commands to modify (add, set, delete) the Exif and\n" - " Iptc metadata of image files or set the Jpeg comment.\n" + " IPTC metadata of image files or set the JPEG comment.\n" " Requires option -c, -m or -M.\n") << _(" fi | fixiso Copy ISO setting from the Nikon Makernote to the regular\n" " Exif tag.\n") @@ -242,8 +242,9 @@ void Params::help(std::ostream& os) const << _(" t : interpreted (translated) Exif data (shortcut for -Pkyct)\n") << _(" v : plain Exif data values (shortcut for -Pxgnycv)\n") << _(" h : hexdump of the Exif data (shortcut for -Pxgnycsh)\n") - << _(" i : Iptc data values\n") - << _(" c : Jpeg comment\n") + << _(" i : IPTC data values\n") + << _(" x : XMP properties\n") + << _(" c : JPEG comment\n") << _(" -P cols Print columns for the Exif taglist ('print' action). Valid are:\n") << _(" x : print a column with the tag value\n") << _(" g : group name\n") @@ -260,13 +261,17 @@ void Params::help(std::ostream& os) const << _(" a : all supported metadata (the default)\n") << _(" e : Exif section\n") << _(" t : Exif thumbnail only\n") - << _(" i : Iptc data\n") - << _(" c : Jpeg comment\n") + << _(" i : IPTC data\n") + << _(" x : XMP packet\n") + << _(" c : JPEG comment\n") << _(" -i tgt Insert target(s) for the 'insert' action. Possible targets are\n" - " the same as those for the -d option. Only Jpeg thumbnails can\n" - " be inserted, they need to be named -thumb.jpg\n") + " the same as those for the -d option, plus:\n" + " X : Insert XMP packet from .xmp\n" + " Only JPEG thumbnails can be inserted, they need to be named\n" + " -thumb.jpg\n") << _(" -e tgt Extract target(s) for the 'extract' action. Possible targets\n" - " are the same as those for the -d option.\n") + " are the same as those for the -i option, plus:\n" + " X : Extract XMP packet to .xmp\n") << _(" -r fmt Filename format for the 'rename' action. The format string\n" " follows strftime(3). The following keywords are supported:\n") << _(" :basename: - original filename without extension\n") @@ -274,7 +279,7 @@ void Params::help(std::ostream& os) const << _(" :parentname: - name of parent directory\n") << _(" Default filename format is ") << format_ << ".\n" - << _(" -c txt Jpeg comment string to set in the image.\n") + << _(" -c txt JPEG comment string to set in the image.\n") << _(" -m file Command file for the modify action. The format for commands is\n" " set|add|del [[] ].\n") << _(" -M cmd Command line for the modify action. The format for the\n" @@ -401,6 +406,7 @@ int Params::evalPrint(const std::string& optarg) case 'v': rc = evalPrintCols("xgnycv"); break; case 'h': rc = evalPrintCols("xgnycsh"); break; case 'i': printMode_ = pmIptc; break; + case 'x': printMode_ = pmXmp; break; case 'c': printMode_ = pmComment; break; default: std::cerr << progname() << ": " << _("Unrecognized print mode") << " `" @@ -780,11 +786,14 @@ namespace { switch (optarg[i]) { case 'e': target |= Params::ctExif; break; case 'i': target |= Params::ctIptc; break; + case 'x': target |= Params::ctXmp; break; + case 'X': target |= Params::ctXmpPacket; break; case 'c': target |= Params::ctComment; break; case 't': target |= Params::ctThumb; break; case 'a': target |= Params::ctExif | Params::ctIptc - | Params::ctComment; break; + | Params::ctComment + | Params::ctXmp; break; default: std::cerr << Params::instance().progname() << ": " << _("Unrecognized ") << action << " " << _("target") << " `" << optarg[i] << "'\n"; diff --git a/src/exiv2.hpp b/src/exiv2.hpp index 283f950a..35292c20 100644 --- a/src/exiv2.hpp +++ b/src/exiv2.hpp @@ -123,7 +123,7 @@ public: void cleanup(); //! Enumerates print modes - enum PrintMode { pmSummary, pmList, pmIptc, pmComment }; + enum PrintMode { pmSummary, pmList, pmIptc, pmXmp, pmComment }; //! Individual items to print enum PrintItem { @@ -141,7 +141,7 @@ public: }; //! Enumerates common targets, bitmap - enum CommonTarget { ctExif = 1, ctIptc = 2, ctComment = 4, ctThumb = 8 }; + enum CommonTarget { ctExif = 1, ctIptc = 2, ctComment = 4, ctThumb = 8, ctXmp = 16, ctXmpPacket = 32 }; //! Enumerates the policies to handle existing files in rename action enum FileExistsPolicy { overwritePolicy, renamePolicy, askPolicy }; @@ -193,7 +193,7 @@ private: printMode_(pmSummary), printItems_(0), action_(0), - target_(ctExif|ctIptc|ctComment), + target_(ctExif|ctIptc|ctComment|ctXmp), adjustment_(0), format_("%Y%m%d_%H%M%S"), formatSet_(false), diff --git a/src/exv_msvc.h b/src/exv_msvc.h index 70a4923f..ba681450 100644 --- a/src/exv_msvc.h +++ b/src/exv_msvc.h @@ -23,13 +23,13 @@ #define EXV_PACKAGE_NAME "exiv2" /* Define to the full name and version of this package. */ -#define EXV_PACKAGE_STRING "exiv2 0.15" +#define EXV_PACKAGE_STRING "exiv2 0.16" /* Define to the one symbol short name of this package. */ #define EXV_PACKAGE_TARNAME "exiv2" /* Define to the version of this package. */ -#define EXV_PACKAGE_VERSION "0.15" +#define EXV_PACKAGE_VERSION "0.16" /* Define to `int' if does not define pid_t. */ #define pid_t int diff --git a/src/image.cpp b/src/image.cpp index 777dc795..e1b2431e 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -74,21 +74,21 @@ EXIV2_RCSID("@(#) $Id$") namespace Exiv2 { const ImageFactory::Registry ImageFactory::registry_[] = { - //image type creation fct type check Exif mode IPTC mode Comment mode - //--------------- --------------- ---------- ----------- ----------- ------------ - { ImageType::jpeg, newJpegInstance, isJpegType, amReadWrite, amReadWrite, amReadWrite }, - { ImageType::exv, newExvInstance, isExvType, amReadWrite, amReadWrite, amReadWrite }, - { ImageType::cr2, newCr2Instance, isCr2Type, amRead, amRead, amNone }, - { ImageType::crw, newCrwInstance, isCrwType, amReadWrite, amNone, amReadWrite }, - { ImageType::mrw, newMrwInstance, isMrwType, amRead, amRead, amNone }, - { ImageType::tiff, newTiffInstance, isTiffType, amRead, amRead, amNone }, - { ImageType::orf, newOrfInstance, isOrfType, amRead, amRead, amNone }, -#ifdef EXV_HAVE_LIBZ - { ImageType::png, newPngInstance, isPngType, amRead, amRead, amNone }, -#endif // EXV_HAVE_LIBZ - { ImageType::raf, newRafInstance, isRafType, amRead, amRead, amNone }, - // End of list marker - { ImageType::none, 0, 0, amNone, amNone, amNone } + //image type creation fct type check Exif mode IPTC mode XMP mode Comment mode + //--------------- --------------- ---------- ----------- ----------- ----------- ------------ + { ImageType::jpeg, newJpegInstance, isJpegType, amReadWrite, amReadWrite, amReadWrite, amReadWrite }, + { ImageType::exv, newExvInstance, isExvType, amReadWrite, amReadWrite, amReadWrite, amReadWrite }, + { ImageType::cr2, newCr2Instance, isCr2Type, amRead, amRead, amRead, amNone }, + { ImageType::crw, newCrwInstance, isCrwType, amReadWrite, amNone, amNone, amReadWrite }, + { ImageType::mrw, newMrwInstance, isMrwType, amRead, amRead, amRead, amNone }, + { ImageType::tiff, newTiffInstance, isTiffType, amRead, amRead, amRead, amNone }, + { ImageType::orf, newOrfInstance, isOrfType, amRead, amRead, amRead, amNone }, +#ifdef EXV_HAVE_LIBZ + { ImageType::png, newPngInstance, isPngType, amRead, amRead, amRead, amNone }, +#endif // EXV_HAVE_LIBZ + { ImageType::raf, newRafInstance, isRafType, amRead, amRead, amRead, amNone }, + // End of list marker + { ImageType::none, 0, 0, amNone, amNone, amNone, amNone } }; bool ImageFactory::Registry::operator==(const int& imageType) const @@ -110,6 +110,7 @@ namespace Exiv2 { clearExifData(); clearIptcData(); clearXmpPacket(); + clearXmpData(); clearComment(); } @@ -117,6 +118,7 @@ namespace Exiv2 { { setExifData(image.exifData()); setIptcData(image.iptcData()); + setXmpData(image.xmpData()); setComment(image.comment()); } @@ -150,6 +152,16 @@ namespace Exiv2 { xmpPacket_ = xmpPacket; } + void Image::clearXmpData() + { + xmpData_.clear(); + } + + void Image::setXmpData(const XmpData& xmpData) + { + xmpData_ = xmpData; + } + void Image::clearComment() { comment_.erase(); @@ -189,6 +201,9 @@ namespace Exiv2 { case mdIptc: am = r->iptcSupport_; break; + case mdXmp: + am = r->xmpSupport_; + break; case mdComment: am = r->commentSupport_; break; diff --git a/src/image.hpp b/src/image.hpp index 866f1f2d..20175d3e 100644 --- a/src/image.hpp +++ b/src/image.hpp @@ -40,6 +40,7 @@ #include "basicio.hpp" #include "exif.hpp" #include "iptc.hpp" +#include "xmp.hpp" // + standard includes #include @@ -151,6 +152,17 @@ namespace Exiv2 { @brief Set the raw XMP packet to \em xmpPacket. */ virtual void clearXmpPacket(); + /*! + @brief Assign new XMP data. The new XMP data is not written + to the image until the writeMetadata() method is called. + @param xmpData An XmpData instance holding XMP data to be copied + */ + virtual void setXmpData(const XmpData& xmpData); + /*! + @brief Erase any buffered XMP data. XMP data is not removed from + the actual image until the writeMetadata() method is called. + */ + virtual void clearXmpData(); /*! @brief Set the image comment. The new comment is not written to the image until the writeMetadata() method is called. @@ -198,6 +210,18 @@ namespace Exiv2 { @return modifiable IptcData instance containing IPTC values */ virtual IptcData& iptcData() { return iptcData_; } + /*! + @brief Returns an XmpData instance containing currently buffered + XMP data. + + The contained XMP data may have been read from the image by + a previous call to readMetadata() or added directly. The XMP + data in the returned instance will be written to the image when + writeMetadata() is called. + + @return modifiable XmpData instance containing XMP values + */ + virtual XmpData& xmpData() { return xmpData_; } /*! @brief Return a modifiable reference to the raw XMP packet. */ @@ -247,6 +271,18 @@ namespace Exiv2 { @return modifiable IptcData instance containing IPTC values */ virtual const IptcData& iptcData() const { return iptcData_; } + /*! + @brief Returns an XmpData instance containing currently buffered + XMP data. + + The contained XMP data may have been read from the image by + a previous call to readMetadata() or added directly. The XMP + data in the returned instance will be written to the image when + writeMetadata() is called. + + @return modifiable XmpData instance containing XMP values + */ + virtual const XmpData& xmpData() const { return xmpData_; } /*! @brief Return a copy of the image comment. May be an empty string. */ @@ -289,6 +325,7 @@ namespace Exiv2 { BasicIo::AutoPtr io_; //!< Image data IO pointer ExifData exifData_; //!< Exif data container IptcData iptcData_; //!< IPTC data container + XmpData xmpData_; //!< XMP data container std::string comment_; //!< User comment std::string xmpPacket_; //!< XMP packet @@ -461,6 +498,7 @@ namespace Exiv2 { IsThisTypeFct isThisType_; AccessMode exifSupport_; AccessMode iptcSupport_; + AccessMode xmpSupport_; AccessMode commentSupport_; }; diff --git a/src/iptc.hpp b/src/iptc.hpp index 09874d8a..dea872f3 100644 --- a/src/iptc.hpp +++ b/src/iptc.hpp @@ -95,10 +95,6 @@ namespace Exiv2 { Calls setValue(const Value*). */ Iptcdatum& operator=(const Value& value); - /*! - @brief Set the Value. This method copies (clones) the %Value pointed - to by \em pValue. - */ void setValue(const Value* pValue); /*! @brief Set the value to the string \em value, using @@ -113,23 +109,12 @@ namespace Exiv2 { //! @name Accessors //@{ - /*! - @brief Write value to a data buffer and return the number - of bytes written. - - The user must ensure that the buffer has enough memory. Otherwise - the call results in undefined behaviour. - - @param buf Data buffer to write to. - @param byteOrder Applicable byte order (little or big endian). - @return Number of characters written. - */ long copy(byte* buf, ByteOrder byteOrder) const { return value_.get() == 0 ? 0 : value_->copy(buf, byteOrder); } /*! @brief Return the key of the Iptcdatum. The key is of the form 'Iptc.recordName.datasetName'. Note however that the key - is not necessarily unique, i.e., an IptcData may contain + is not necessarily unique, i.e., an IptcData object may contain multiple metadata with the same key. */ std::string key() const { return key_.get() == 0 ? "" : key_->key(); } @@ -156,78 +141,24 @@ namespace Exiv2 { //! Return the tag (aka dataset) number uint16_t tag() const { return key_.get() == 0 ? 0 : key_->tag(); } - //! Return the type id of the value TypeId typeId() const { return value_.get() == 0 ? invalidTypeId : value_->typeId(); } - //! Return the name of the type const char* typeName() const { return TypeInfo::typeName(typeId()); } - //! Return the size in bytes of one component of this type long typeSize() const { return TypeInfo::typeSize(typeId()); } - //! Return the number of components in the value long count() const { return value_.get() == 0 ? 0 : value_->count(); } - //! Return the size of the value in bytes long size() const { return value_.get() == 0 ? 0 : value_->size(); } - //! Return the value as a string. std::string toString() const { return value_.get() == 0 ? "" : value_->toString(); } - /*! - @brief Return the n-th component of the value converted to long. The - return value is -1 if the value of the Iptcdatum is not set and - the behaviour of the method is undefined if there is no n-th - component. - */ + std::string toString(long n) const + { return value_.get() == 0 ? "" : value_->toString(n); } long toLong(long n =0) const { return value_.get() == 0 ? -1 : value_->toLong(n); } - /*! - @brief Return the n-th component of the value converted to float. The - return value is -1 if the value of the Iptcdatum is not set and - the behaviour of the method is undefined if there is no n-th - component. - */ float toFloat(long n =0) const { return value_.get() == 0 ? -1 : value_->toFloat(n); } - /*! - @brief Return the n-th component of the value converted to - Rational. The return value is -1/1 if the value of the - Iptcdatum is not set and the behaviour of the method is - undefined if there is no n-th component. - */ Rational toRational(long n =0) const { return value_.get() == 0 ? Rational(-1, 1) : value_->toRational(n); } - /*! - @brief Return an auto-pointer to a copy (clone) of the value. The - caller owns this copy and the auto-pointer ensures that it will - be deleted. - - This method is provided for users who need full control over the - value. A caller may, e.g., downcast the pointer to the appropriate - subclass of Value to make use of the interface of the subclass to set - or modify its contents. - - @return An auto-pointer to a copy (clone) of the value, 0 if the value - is not set. - */ Value::AutoPtr getValue() const { return value_.get() == 0 ? Value::AutoPtr(0) : value_->clone(); } - /*! - @brief Return a constant reference to the value. - - This method is provided mostly for convenient and versatile output of - the value which can (to some extent) be formatted through standard - stream manipulators. Do not attempt to write to the value through - this reference. - - Example:
- @code - IptcData::const_iterator i = iptcData.findKey(key); - if (i != iptcData.end()) { - std::cout << i->key() << " " << std::hex << i->value() << "\n"; - } - @endcode - - @return A constant reference to the value. - @throw Error If the value is not set. - */ const Value& value() const; //@} diff --git a/src/jpgimage.cpp b/src/jpgimage.cpp index 93177d67..64de2525 100644 --- a/src/jpgimage.cpp +++ b/src/jpgimage.cpp @@ -291,7 +291,12 @@ namespace Exiv2 { io_->read(xmpPacket.pData_, xmpPacket.size_); if (io_->error() || io_->eof()) throw Error(14); xmpPacket_.assign(reinterpret_cast(xmpPacket.pData_), xmpPacket.size_); - // Todo: Update here when merging branches/xmp + if (XmpParser::decode(xmpData_, xmpPacket_)) { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: Failed to decode XMP metadata.\n"; +#endif + xmpData_.clear(); + } --search; } else if ( marker == app13_ diff --git a/src/metadatum.hpp b/src/metadatum.hpp index fa0e8787..9ba7ac59 100644 --- a/src/metadatum.hpp +++ b/src/metadatum.hpp @@ -146,7 +146,8 @@ namespace Exiv2 { /*! @brief Set the value to the string buf. Uses Value::read(const std::string& buf). If the metadatum does - not have a value yet, then an AsciiValue is created. + not have a value yet, then one is created. See subclasses for + more details. */ virtual void setValue(const std::string& buf) =0; //@} @@ -168,8 +169,8 @@ namespace Exiv2 { /*! @brief Return the key of the metadatum. The key is of the form 'familyName.ifdItem.tagName'. Note however that the key - is not necessarily unique, i.e., an ExifData may contain - multiple metadata with the same key. + is not necessarily unique, i.e., an ExifData object may + contain multiple metadata with the same key. */ virtual std::string key() const =0; //! Return the name of the tag (which is also the third part of the key) @@ -191,24 +192,27 @@ namespace Exiv2 { //! Return the value as a string. virtual std::string toString() const =0; /*! - @brief Return the n-th component of the value converted to long. The - return value is -1 if the value of the Metadatum is not set and - the behaviour of the method is undefined if there is no n-th - component. + @brief Return the n-th component of the value converted to + a string. The behaviour of the method is undefined if there + is no n-th component. + */ + virtual std::string toString(long n) const =0; + /*! + @brief Return the n-th component of the value converted to long. + The return value is -1 if the value is not set and the behaviour + of the method is undefined if there is no n-th component. */ virtual long toLong(long n =0) const =0; /*! - @brief Return the n-th component of the value converted to float. The - return value is -1 if the value of the Metadatum is not set and - the behaviour of the method is undefined if there is no n-th - component. + @brief Return the n-th component of the value converted to float. + The return value is -1 if the value is not set and the behaviour + of the method is undefined if there is no n-th component. */ virtual float toFloat(long n =0) const =0; /*! - @brief Return the n-th component of the value converted to - Rational. The return value is -1/1 if the value of the - Metadatum is not set and the behaviour of the method is - undefined if there is no n-th component. + @brief Return the n-th component of the value converted to Rational. + The return value is -1/1 if the value is not set and the behaviour + of the method is undefined if there is no n-th component. */ virtual Rational toRational(long n =0) const =0; /*! diff --git a/src/minoltamn.hpp b/src/minoltamn.hpp index d3e2ed2b..0bb3a52a 100644 --- a/src/minoltamn.hpp +++ b/src/minoltamn.hpp @@ -24,13 +24,13 @@ Minolta Makernote Format Specification by Dalibor Jelinek,
Minolta Makernote list by Phil Harvey
Minolta Makernote list from PHP JPEG Metadata Toolkit
- Email communication with caulier dot gilles at kdemail dot net
+ Email communication with caulier dot gilles at gmail dot com
Some Minolta camera settings have been decoded by Xavier Raynaud from digiKam project and added by Gilles Caulier. @version $Rev$ @author Andreas Huggel (ahu) ahuggel@gmx.net @author Gilles Caulier (gc) - caulier dot gilles at kdemail dot net + caulier dot gilles at gmail dot com @date 06-May-06, gc: submitted */ #ifndef MINOLTAMN_HPP_ diff --git a/src/pngchunk.cpp b/src/pngchunk.cpp index f2c3edff..2635a313 100644 --- a/src/pngchunk.cpp +++ b/src/pngchunk.cpp @@ -36,7 +36,7 @@ EXIV2_RCSID("@(#) $Id: pngchunk.cpp 823 2006-06-23 07:35:00Z cgilles $") # include "exv_conf.h" #endif -//#define DEBUG 1 +#define DEBUG 1 // some defines to make it easier #define PNG_CHUNK_TYPE(data, index) &data[index+4] @@ -73,9 +73,9 @@ PNG tags : http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PN // class member definitions namespace Exiv2 { - void PngChunk::decode(Image* pImage, - const byte* pData, - long size) + void PngChunk::decode(Image* pImage, + const byte* pData, + long size) { assert(pImage != 0); assert(pData != 0); @@ -88,7 +88,8 @@ namespace Exiv2 { { while (index < size-PNG_CHUNK_HEADER_SIZE && strncmp((char*)PNG_CHUNK_TYPE(pData, index), "tEXt", 4) && - strncmp((char*)PNG_CHUNK_TYPE(pData, index), "zTXt", 4)) + strncmp((char*)PNG_CHUNK_TYPE(pData, index), "zTXt", 4) && + strncmp((char*)PNG_CHUNK_TYPE(pData, index), "iTXt", 4)) { if (!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "IEND", 4)) throw Error(14); @@ -98,7 +99,7 @@ namespace Exiv2 { if (index < size-PNG_CHUNK_HEADER_SIZE) { - // we found a tEXt or zTXt field + // we found a tEXt, zTXt, or iTXt field // get the key, it's a null terminated string at the chunk start const byte *key = &PNG_CHUNK_DATA(pData, index, 0); @@ -111,158 +112,15 @@ namespace Exiv2 { throw Error(14); } - DataBuf arr; - - if(!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "zTXt", 4)) - { - // Extract a deflate compressed Latin-1 text chunk + DataBuf arr = parsePngChunk(pData, size, index, keysize); #ifdef DEBUG - std::cerr << "Exiv2::PngChunk::decode: We found a zTXt field\n"; -#endif - // we get the compression method after the key - const byte* compressionMethod = &PNG_CHUNK_DATA(pData, index, keysize+1); - if ( *compressionMethod != 0x00 ) - { - // then it isn't zlib compressed and we are sunk -#ifdef DEBUG - std::cerr << "Exiv2::PngChunk::decode: Non-standard compression method.\n"; -#endif - throw Error(14); - } - - // compressed string after the compression technique spec - const byte* compressedText = &PNG_CHUNK_DATA(pData, index, keysize+2); - unsigned int compressedTextSize = getLong(&pData[index], bigEndian)-keysize-2; - - // security check, also considering overflow wraparound from the addition -- - // we may endup with a /smaller/ index if we wrap all the way around - long firstIndex = (long)(compressedText - pData); - long onePastLastIndex = firstIndex + compressedTextSize; - if ( onePastLastIndex > size || onePastLastIndex <= firstIndex) - throw Error(14); - - uLongf uncompressedLen = compressedTextSize * 2; // just a starting point - int zlibResult; - - do - { - arr.alloc(uncompressedLen); - zlibResult = uncompress((Bytef*)arr.pData_, &uncompressedLen, - compressedText, compressedTextSize); - - if (Z_OK == zlibResult) - { - // then it is all OK - arr.alloc(uncompressedLen); - } - else if (Z_BUF_ERROR == zlibResult) - { - // the uncompressedArray needs to be larger -#ifdef DEBUG - std::cerr << "Exiv2::PngChunk::decode: doubling size for decompression.\n"; -#endif - uncompressedLen *= 2; - - // DoS protection. can't be bigger than 64k - if ( uncompressedLen > 131072 ) - break; - } - else - { - // something bad happened - throw Error(14); - } - } - while (Z_BUF_ERROR == zlibResult); - - if (zlibResult != Z_OK) - throw Error(14); - } - else if (!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "tEXt", 4)) - { - // Extract a non-compressed Latin-1 text chunk -#ifdef DEBUG - std::cerr << "Exiv2::PngChunk::decode: We found a tEXt field\n"; -#endif - // the text comes after the key, but isn't null terminated - const byte* text = &PNG_CHUNK_DATA(pData, index, keysize+1); - long textsize = getLong(&pData[index], bigEndian)-keysize-1; - - // security check, also considering overflow wraparound from the addition -- - // we may endup with a /smaller/ index if we wrap all the way around - long firstIndex = (long)(text - pData); - long onePastLastIndex = firstIndex + textsize; - - if ( onePastLastIndex > size || onePastLastIndex <= firstIndex) - throw Error(14); - - arr.alloc(textsize); - arr = DataBuf(text, textsize); - } - else - { - // TODO : Add 'iTXt' chunk 'Description' tag support here - -#ifdef DEBUG - std::cerr << "Exiv2::PngChunk::decode: We found a field, not expected though\n"; -#endif - throw Error(14); - } - -#ifdef DEBUG - std::cerr << "Exiv2::PngChunk::decode: Found PNG entry " << std::string((const char*)key) << " / " - << std::string((const char*)arr.pData_, 64) << "\n"; + std::cerr << "Exiv2::PngChunk::decode: Found PNG chunk: " + << std::string((const char*)key) << " :: " + << std::string((const char*)arr.pData_, 32) << "\n"; #endif - // We look if an EXIF raw profile exist. - - if ( memcmp("Raw profile type exif", key, 21) == 0 || - memcmp("Raw profile type APP1", key, 21) == 0 ) - { - DataBuf exifData = readRawProfile(arr); - long length = exifData.size_; - - if (length > 0) - { - // Find the position of Exif header in bytes array. - - const byte exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; - long pos = -1; - - for (long i=0 ; i < length-(long)sizeof(exifHeader) ; i++) - { - if (memcmp(exifHeader, &exifData.pData_[i], sizeof(exifHeader)) == 0) - { - pos = i; - break; - } - } - - // If found it, store only these data at from this place. - - if (pos !=-1) - { -#ifdef DEBUG - std::cerr << "Exiv2::PngChunk::decode: Exif header found at position " << pos << "\n"; -#endif - pos = pos + sizeof(exifHeader); - TiffParser::decode(pImage, exifData.pData_ + pos, length - pos, - TiffCreator::create, TiffDecoder::findDecoder); - } - } - } - - // We look if an IPTC raw profile exist. - - if ( memcmp("Raw profile type iptc", key, 21) == 0 ) - { - DataBuf iptcData = readRawProfile(arr); - long length = iptcData.size_; - - if (length > 0) - pImage->iptcData().load(iptcData.pData_, length); - } + parseChunkContent(pImage, key, arr); index += getLong(&pData[index], bigEndian) + PNG_CHUNK_HEADER_SIZE; } @@ -270,6 +128,261 @@ namespace Exiv2 { } // PngChunk::decode + DataBuf PngChunk::parsePngChunk(const byte* pData, long size, long& index, int keysize) + { + DataBuf arr; + + if(!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "zTXt", 4)) + { + // Extract a deflate compressed Latin-1 text chunk + +#ifdef DEBUG + std::cerr << "Exiv2::PngChunk::parsePngChunk: We found a zTXt field\n"; +#endif + // we get the compression method after the key + const byte* compressionMethod = &PNG_CHUNK_DATA(pData, index, keysize+1); + if ( *compressionMethod != 0x00 ) + { + // then it isn't zlib compressed and we are sunk +#ifdef DEBUG + std::cerr << "Exiv2::PngChunk::parsePngChunk: Non-standard zTXt compression method.\n"; +#endif + throw Error(14); + } + + // compressed string after the compression technique spec + const byte* compressedText = &PNG_CHUNK_DATA(pData, index, keysize+2); + unsigned int compressedTextSize = getLong(&pData[index], bigEndian)-keysize-2; + + // security check, also considering overflow wraparound from the addition -- + // we may endup with a /smaller/ index if we wrap all the way around + long firstIndex = (long)(compressedText - pData); + long onePastLastIndex = firstIndex + compressedTextSize; + if ( onePastLastIndex > size || onePastLastIndex <= firstIndex) + throw Error(14); + + zlibUncompress(compressedText, compressedTextSize, arr); + } + else if (!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "tEXt", 4)) + { + // Extract a non-compressed Latin-1 text chunk +#ifdef DEBUG + std::cerr << "Exiv2::PngChunk::parsePngChunk: We found a tEXt field\n"; +#endif + // the text comes after the key, but isn't null terminated + const byte* text = &PNG_CHUNK_DATA(pData, index, keysize+1); + long textsize = getLong(&pData[index], bigEndian)-keysize-1; + + // security check, also considering overflow wraparound from the addition -- + // we may endup with a /smaller/ index if we wrap all the way around + long firstIndex = (long)(text - pData); + long onePastLastIndex = firstIndex + textsize; + + if ( onePastLastIndex > size || onePastLastIndex <= firstIndex) + throw Error(14); + + arr.alloc(textsize); + arr = DataBuf(text, textsize); + } + else if(!strncmp((char*)PNG_CHUNK_TYPE(pData, index), "iTXt", 4)) + { + // Extract a deflate compressed or uncompressed UTF-8 text chunk + + // we get the compression flag after the key + const byte* compressionFlag = &PNG_CHUNK_DATA(pData, index, keysize+1); + // we get the compression method after the compression flag + const byte* compressionMethod = &PNG_CHUNK_DATA(pData, index, keysize+1); + // language description string after the compression technique spec + const byte* languageText = &PNG_CHUNK_DATA(pData, index, keysize+1); + unsigned int languageTextSize = getLong(&pData[index], bigEndian)-keysize-1; + // translated keyword string after the language description + const byte* translatedKeyText = &PNG_CHUNK_DATA(pData, index, keysize+1); + unsigned int translatedKeyTextSize = getLong(&pData[index], bigEndian)-keysize-1; + + if ( *compressionFlag == 0x00 ) + { + // then it's an uncompressed iTXt chunk +#ifdef DEBUG + std::cerr << "Exiv2::PngChunk::parsePngChunk: We found an uncompressed iTXt field\n"; +#endif + + // the text comes after the translated keyword, but isn't null terminated + const byte* text = &PNG_CHUNK_DATA(pData, index, keysize+1); + long textsize = getLong(&pData[index], bigEndian)-keysize-1; + + // security check, also considering overflow wraparound from the addition -- + // we may endup with a /smaller/ index if we wrap all the way around + long firstIndex = (long)(text - pData); + long onePastLastIndex = firstIndex + textsize; + + if ( onePastLastIndex > size || onePastLastIndex <= firstIndex) + throw Error(14); + + arr.alloc(textsize); + arr = DataBuf(text, textsize); + } + else if ( *compressionMethod == 0x00 ) + { + // then it's a zlib compressed iTXt chunk +#ifdef DEBUG + std::cerr << "Exiv2::PngChunk::parsePngChunk: We found a zlib compressed iTXt field\n"; +#endif + + // the compressed text comes after the translated keyword, but isn't null terminated + const byte* compressedText = &PNG_CHUNK_DATA(pData, index, keysize+1); + long compressedTextSize = getLong(&pData[index], bigEndian)-keysize-1; + + // security check, also considering overflow wraparound from the addition -- + // we may endup with a /smaller/ index if we wrap all the way around + long firstIndex = (long)(compressedText - pData); + long onePastLastIndex = firstIndex + compressedTextSize; + if ( onePastLastIndex > size || onePastLastIndex <= firstIndex) + throw Error(14); + + zlibUncompress(compressedText, compressedTextSize, arr); + } + else + { + // then it isn't zlib compressed and we are sunk +#ifdef DEBUG + std::cerr << "Exiv2::PngChunk::parsePngChunk: Non-standard iTXt compression method.\n"; +#endif + throw Error(14); + } + } + else + { +#ifdef DEBUG + std::cerr << "Exiv2::PngChunk::parsePngChunk: We found a field, not expected though\n"; +#endif + throw Error(14); + } + + return arr; + + } // PngChunk::parsePngChunk + + void PngChunk::parseChunkContent(Image* pImage, const byte *key, const DataBuf arr) + { + // We look if an ImageMagick EXIF raw profile exist. + + if ( (memcmp("Raw profile type exif", key, 21) == 0 || + memcmp("Raw profile type APP1", key, 21) == 0) && + pImage->exifData().empty()) + { + DataBuf exifData = readRawProfile(arr); + long length = exifData.size_; + + if (length > 0) + { + // Find the position of Exif header in bytes array. + + const byte exifHeader[] = { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; + long pos = -1; + + for (long i=0 ; i < length-(long)sizeof(exifHeader) ; i++) + { + if (memcmp(exifHeader, &exifData.pData_[i], sizeof(exifHeader)) == 0) + { + pos = i; + break; + } + } + + // If found it, store only these data at from this place. + + if (pos !=-1) + { +#ifdef DEBUG + std::cerr << "Exiv2::PngChunk::decode: Exif header found at position " << pos << "\n"; +#endif + pos = pos + sizeof(exifHeader); + TiffParser::decode(pImage, exifData.pData_ + pos, length - pos, + TiffCreator::create, TiffDecoder::findDecoder); + } + } + } + + // We look if an ImageMagick IPTC raw profile exist. + + if ( memcmp("Raw profile type iptc", key, 21) == 0 && + pImage->iptcData().empty()) + { + DataBuf iptcData = readRawProfile(arr); + long length = iptcData.size_; + + if (length > 0) + pImage->iptcData().load(iptcData.pData_, length); + } + + // We look if an ImageMagick XMP raw profile exist. + + if ( memcmp("Raw profile type xmp", key, 20) == 0 && + pImage->xmpData().empty()) + { + DataBuf xmpBuf = readRawProfile(arr); + long length = xmpBuf.size_; + + if (length > 0) + { + std::string& xmpPacket = pImage->xmpPacket(); + xmpPacket.assign(reinterpret_cast(xmpBuf.pData_), length); + std::string::size_type idx = xmpPacket.find_first_of('<'); + if (idx != std::string::npos && idx > 0) + { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: Removing " << idx << " characters " + << "from the beginning of the XMP packet\n"; +#endif + xmpPacket = xmpPacket.substr(idx); + } + if (XmpParser::decode(pImage->xmpData(), xmpPacket)) + { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: Failed to decode XMP metadata.\n"; +#endif + } + } + } + + // We look if an Adobe XMP string exist. + + if ( memcmp("XML:com.adobe.xmp", key, 17) == 0 && + pImage->xmpData().empty()) + { + if (arr.size_ > 0) + { + std::string& xmpPacket = pImage->xmpPacket(); + xmpPacket.assign(reinterpret_cast(arr.pData_), arr.size_); + std::string::size_type idx = xmpPacket.find_first_of('<'); + if (idx != std::string::npos && idx > 0) + { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: Removing " << idx << " characters " + << "from the beginning of the XMP packet\n"; +#endif + xmpPacket = xmpPacket.substr(idx); + } + if (XmpParser::decode(pImage->xmpData(), xmpPacket)) + { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: Failed to decode XMP metadata.\n"; +#endif + } + } + } + + // We look if a comments string exist. Note than we use only 'Description' keyword which + // is dedicaced to store long comments. 'Comment' keyword is ignored. + + if ( memcmp("Description", key, 11) == 0 && + pImage->comment().empty()) + { + pImage->comment().assign(reinterpret_cast(arr.pData_), arr.size_); + } + + } // PngChunk::parseChunkContent + DataBuf PngChunk::readRawProfile(const DataBuf& text) { DataBuf info; @@ -349,5 +462,232 @@ namespace Exiv2 { } return info; + } // PngChunk::readRawProfile + + void PngChunk::zlibUncompress(const byte* compressedText, + unsigned int compressedTextSize, + DataBuf& arr) + { + uLongf uncompressedLen = compressedTextSize * 2; // just a starting point + int zlibResult; + + do + { + arr.alloc(uncompressedLen); + zlibResult = uncompress((Bytef*)arr.pData_, &uncompressedLen, + compressedText, compressedTextSize); + + if (zlibResult == Z_OK) + { + // then it is all OK + arr.alloc(uncompressedLen); + } + else if (zlibResult == Z_BUF_ERROR) + { + // the uncompressedArray needs to be larger +#ifdef DEBUG + std::cerr << "Exiv2::PngChunk::parsePngChunk: doubling size for decompression.\n"; +#endif + uncompressedLen *= 2; + + // DoS protection. can't be bigger than 64k + if ( uncompressedLen > 131072 ) + break; + } + else + { + // something bad happened + throw Error(14); + } + } + while (zlibResult == Z_BUF_ERROR); + + if (zlibResult != Z_OK) + throw Error(14); + + } // PngChunk::zlibUncompress + +/* TODO : code backported from digiKam. Not yet adapted and used. + + void PngChunk::writeRawProfile(png_struct *ping, + png_info* ping_info, + char* profile_type, + char* profile_data, + png_uint_32 length) + { + png_textp text; + + register long i; + + uchar *sp; + + png_charp dp; + + png_uint_32 allocated_length, description_length; + + const uchar hex[16] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; + + DDebug() << "Writing Raw profile: type=" << profile_type << ", length=" << length << endl; + + text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text)); + description_length = strlen((const char *) profile_type); + allocated_length = (png_uint_32) (length*2 + (length >> 5) + 20 + description_length); + + text[0].text = (png_charp) png_malloc(ping, allocated_length); + text[0].key = (png_charp) png_malloc(ping, (png_uint_32) 80); + text[0].key[0] = '\0'; + + concatenateString(text[0].key, "Raw profile type ", 4096); + concatenateString(text[0].key, (const char *) profile_type, 62); + + sp = (uchar*)profile_data; + dp = text[0].text; + *dp++='\n'; + + copyString(dp, (const char *) profile_type, allocated_length); + + dp += description_length; + *dp++='\n'; + + formatString(dp, allocated_length-strlen(text[0].text), "%8lu ", length); + + dp += 8; + + for (i=0; i < (long) length; i++) + { + if (i%36 == 0) + *dp++='\n'; + + *(dp++)=(char) hex[((*sp >> 4) & 0x0f)]; + *(dp++)=(char) hex[((*sp++ ) & 0x0f)]; + } + + *dp++='\n'; + *dp='\0'; + text[0].text_length = (png_size_t) (dp-text[0].text); + text[0].compression = -1; + + if (text[0].text_length <= allocated_length) + png_set_text(ping, ping_info,text, 1); + + png_free(ping, text[0].text); + png_free(ping, text[0].key); + png_free(ping, text); + + } // PngChunk::writeRawProfile + + size_t PngChunk::concatenateString(char* destination, + const char* source, + const size_t length) + { + register char *q; + + register const char *p; + + register size_t i; + + size_t count; + + if ( !destination || !source || length == 0 ) + return 0; + + p = source; + q = destination; + i = length; + + while ((i-- != 0) && (*q != '\0')) + q++; + + count = (size_t) (q-destination); + i = length-count; + + if (i == 0) + return(count+strlen(p)); + + while (*p != '\0') + { + if (i != 1) + { + *q++=(*p); + i--; + } + p++; + } + + *q='\0'; + + return(count+(p-source)); + + } // PngChunk::concatenateString + + size_t PngChunk::copyString(char* destination, + const char* source, + const size_t length) + { + register char *q; + + register const char *p; + + register size_t i; + + if ( !destination || !source || length == 0 ) + return 0; + + p = source; + q = destination; + i = length; + + if ((i != 0) && (--i != 0)) + { + do + { + if ((*q++=(*p++)) == '\0') + break; + } + while (--i != 0); + } + + if (i == 0) + { + if (length != 0) + *q='\0'; + + while (*p++ != '\0'); + } + + return((size_t) (p-source-1)); + + } // PngChunk::copyString + + long PngChunk::formatString(char* string, + const size_t length, + const char* format, + ...) + { + long n; + + va_list operands; + + va_start(operands,format); + n = (long) formatStringList(string, length, format, operands); + va_end(operands); + return(n); + + } // PngChunk::formatString + + long PngChunk::formatStringList(char* string, + const size_t length, + const char* format, + va_list operands) + { + int n = vsnprintf(string, length, format, operands); + + if (n < 0) + string[length-1] = '\0'; + + return((long) n); + } // PngChunk::formatStringList +*/ + } // namespace Exiv2 diff --git a/src/pngchunk.hpp b/src/pngchunk.hpp index f1d8a248..e94e69a6 100644 --- a/src/pngchunk.hpp +++ b/src/pngchunk.hpp @@ -20,12 +20,16 @@ */ /*! @file pngchunk.hpp - @brief Class PngChunk to parse PNG chunk data. + @brief Class PngChunk to parse PNG chunk data implemented using the following references:
+ PNG iTXt chunk structure from PNG definitive guide,
+ PNG tTXt and zTXt chunks structures from PNG definitive guide,
+ PNG tags list by Phil Harvey
+ Email communication with caulier dot gilles at gmail dot com
@version $Rev: 823 $ @author Andreas Huggel (ahu) ahuggel@gmx.net @author Gilles Caulier (gc) - caulier dot gilles at kdemail dot net + caulier dot gilles at gmail dot com @date 12-Jun-06, gc: submitted */ #ifndef PNGCHUNK_HPP_ @@ -66,19 +70,69 @@ namespace Exiv2 { no checks are performed. @param size Length of the data buffer. */ - static void decode(Image* pImage, - const byte* pData, - long size); + static void decode(Image* pImage, + const byte* pData, + long size); - private: + private: //! @name Accessors //@{ /*! - @brief Todo: Decode ImageMagick raw text profile including encoded Exif/Iptc metadata byte array. + @brief Parse PNG chunk to determine type and extract content. + Supported Chunk types are tTXt, zTXt, and iTXt. + */ + static DataBuf parsePngChunk(const byte* pData, + long size, + long& index, + int keysize); + + /*! + @brief Parse PNG chunk contents to extract metadata container and assign it to image. + Supported contents are: + Exif raw text profile generated by ImageMagick ==> Image Exif metadata. + Iptc raw text profile generated by ImageMagick ==> Image Iptc metadata. + Xmp raw text profile generated by ImageMagick ==> Image Xmp metadata. + Xmp packet generated by Adobe ==> Image Xmp metadata. + Description string ==> Image Comments. + */ + static void parseChunkContent(Image* pImage, + const byte* key, + const DataBuf arr); + + /*! + @brief Decode from ImageMagick raw text profile which host encoded Exif/Iptc/Xmp metadata byte array. */ static DataBuf readRawProfile(const DataBuf& text); + /*! + @brief Wrapper around zlib to uncompress a PNG chunk content. + */ + static void zlibUncompress(const byte* compressedText, + unsigned int compressedTextSize, + DataBuf& arr); + +/* TODO : code backported from digiKam. Not yet adapted and used. + + static DataBuf writeRawProfile(const DataBuf& text); + + static size_t concatenateString(char* destination, + const char* source, + const size_t length); + + static size_t copyString(char* destination, + const char* source, + const size_t length); + + static long formatString(char* string, + const size_t length, + const char* format, + ...); + + static long formatStringList(char *string, + const size_t length, + const char *format, + va_list operands);*/ //@} }; // class PngChunk diff --git a/src/pngimage.hpp b/src/pngimage.hpp index 65ae2345..401c2c01 100644 --- a/src/pngimage.hpp +++ b/src/pngimage.hpp @@ -27,7 +27,7 @@ @author Andreas Huggel (ahu) ahuggel@gmx.net @author Gilles Caulier (gc) - caulier dot gilles at kdemail dot net + caulier dot gilles at gmail dot com @date 12-Jun-06, gc: submitted */ #ifndef PNGIMAGE_HPP_ diff --git a/src/properties.cpp b/src/properties.cpp new file mode 100644 index 00000000..e93534f3 --- /dev/null +++ b/src/properties.cpp @@ -0,0 +1,906 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2007 Andreas Huggel + * + * This program is part of the Exiv2 distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. + */ +/* + File: properties.cpp + Version: $Rev$ + Author(s): Andreas Huggel (ahu) + History: 13-July-07, ahu: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id$") + +// ***************************************************************************** +// included header files +#include "properties.hpp" +#include "tags.hpp" +#include "error.hpp" +#include "types.hpp" +#include "value.hpp" +#include "metadatum.hpp" +#include "i18n.h" // NLS support. + +#include +#include +#include + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + extern const XmpPropertyInfo xmpDcInfo[]; + extern const XmpPropertyInfo xmpXmpInfo[]; + extern const XmpPropertyInfo xmpXmpRightsInfo[]; + extern const XmpPropertyInfo xmpXmpMMInfo[]; + extern const XmpPropertyInfo xmpXmpBJInfo[]; + extern const XmpPropertyInfo xmpXmpTPgInfo[]; + extern const XmpPropertyInfo xmpPhotoshopInfo[]; + extern const XmpPropertyInfo xmpXmpDMInfo[]; + extern const XmpPropertyInfo xmpPdfInfo[]; + extern const XmpPropertyInfo xmpTiffInfo[]; + extern const XmpPropertyInfo xmpExifInfo[]; + + extern const XmpNsInfo xmpNsInfo[] = { + // Schemas + { "http://purl.org/dc/elements/1.1/", "dc", xmpDcInfo, "Dublin Core schema" }, + { "http://ns.adobe.com/xap/1.0/", "xmp", xmpXmpInfo, "XMP Basic schema" }, + { "http://ns.adobe.com/xap/1.0/rights/", "xmpRights", xmpXmpRightsInfo, "XMP Rights Management schema" }, + { "http://ns.adobe.com/xap/1.0/mm/", "xmpMM", xmpXmpMMInfo, "XMP Media Management schema" }, + { "http://ns.adobe.com/xap/1.0/bj/", "xmpBJ", xmpXmpBJInfo, "XMP Basic Job Ticket schema" }, + { "http://ns.adobe.com/xap/1.0/t/pg/", "xmpTPg", xmpXmpTPgInfo, "XMP Paged-Text schema" }, + { "http://ns.adobe.com/xmp/1.0/DynamicMedia/", "xmpDM", xmpXmpDMInfo, "XMP Dynamic Media schema" }, + { "http://ns.adobe.com/pdf/1.3/", "pdf", xmpPdfInfo, "Adobe PDF schema" }, + { "http://ns.adobe.com/photoshop/1.0/", "photoshop", xmpPhotoshopInfo, "Adobe photoshop schema" }, + { "http://ns.adobe.com/camera-raw-settings/1.0/", "crs", 0, "Camera Raw schema" }, + { "http://ns.adobe.com/tiff/1.0/", "tiff", xmpTiffInfo, "Exif Schema for TIFF Properties" }, + { "http://ns.adobe.com/exif/1.0/", "exif", xmpExifInfo, "Exif schema for Exif-specific Properties" }, + { "http://ns.adobe.com/exif/1.0/aux/", "aux", 0, "Exif schema for Additional Exif Properties" }, + { "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/", "iptc" /*Iptc4xmpCore*/, 0, "IPTC Core schema" }, + + // Structures + { "http://ns.adobe.com/xap/1.0/g/", "xapG", 0, "Colorant structure" }, + { "http://ns.adobe.com/xap/1.0/sType/Dimensions#", "stDim", 0, "Dimensions structure" }, + { "http://ns.adobe.com/xap/1.0/sType/Font#", "stFnt", 0, "Font structure" }, + { "http://ns.adobe.com/xap/1.0/g/img/", "xapGImg", 0, "Thumbnail structure" }, + { "http://ns.adobe.com/xap/1.0/sType/ResourceEvent#", "stEvt", 0, "Resource Event structure" }, + { "http://ns.adobe.com/xap/1.0/sType/ResourceRef#", "stRef", 0, "ResourceRef structure" }, + { "http://ns.adobe.com/xap/1.0/sType/Version#", "stVer", 0, "Version structure" }, + { "http://ns.adobe.com/xap/1.0/sType/Job#", "stJob", 0, "Basic Job/Workflow structure" }, + + // Qualifiers + { "http://ns.adobe.com/xmp/Identifier/qual/1.0/", "xmpidq", 0, "Qualifier for xmp:Identifier" } + }; + + extern const XmpPropertyInfo xmpDcInfo[] = { + { "contributor", "contributor", "bag ProperName", xmpText, xmpExternal, "Contributors to the resource (other than the authors)." }, + { "coverage", "coverage", "Text", xmpText, xmpExternal, "The extent or scope of the resource." }, + { "creator", "creator", "seq ProperName", xmpText, xmpExternal, "The authors of the resource (listed in order of precedence, if significant)." }, + { "date", "date", "seq Date", xmpText, xmpExternal, "Date(s) that something interesting happened to the resource." }, + { "description", "description", "Lang Alt", xmpText, xmpExternal, "A textual description of the content of the resource. Multiple values may be " + "present for different languages." }, + { "format", "format", "MIMEType", xmpText, xmpInternal, "The file format used when saving the resource. Tools and applications should set " + "this property to the save format of the data. It may include appropriate qualifiers." }, + { "identifier", "identifier", "Text", xmpText, xmpExternal, "Unique identifier of the resource." }, + { "language", "language", "bag Locale", xmpText, xmpInternal, "An unordered array specifying the languages used in the resource." }, + { "publisher", "publisher", "bag ProperName", xmpText, xmpExternal, "Publishers." }, + { "relation", "relation", "bag Text", xmpText, xmpInternal, "Relationships to other documents." }, + { "rights", "rights", "Lang Alt", xmpText, xmpExternal, "Informal rights statement, selected by language." }, + { "source", "source", "Text", xmpText, xmpExternal, "Unique identifier of the work from which this resource was derived." }, + { "subject", "subject", "bag Text", xmpText, xmpExternal, "An unordered array of descriptive phrases or keywords that specify the topic of the " + "content of the resource." }, + { "title", "title", "Lang Alt", xmpText, xmpExternal, "The title of the document, or the name given to the resource. Typically, it will be " + "a name by which the resource is formally known." }, + { "type", "type", "bag open Choice", xmpText, xmpExternal, "A document type; for example, novel, poem, or working paper." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + extern const XmpPropertyInfo xmpXmpInfo[] = { + { "Advisory", "Advisory", "bag XPath", xmpText, xmpExternal, "An unordered array specifying properties that were edited outside the authoring " + "application. Each item should contain a single namespace and XPath separated by " + "one ASCII space (U+0020)." }, + { "BaseURL", "BaseURL", "URL", xmpText, xmpInternal, "The base URL for relative URLs in the document content. If this document contains " + "Internet links, and those links are relative, they are relative to this base URL. " + "This property provides a standard way for embedded relative URLs to be interpreted " + "by tools. Web authoring tools should set the value based on their notion of where " + "URLs will be interpreted." }, + { "CreateDate", "CreateDate", "Date", xmpText, xmpInternal, "The date and time the resource was originally created." }, + { "CreatorTool", "CreatorTool", "AgentName", xmpText, xmpInternal, "The name of the first known tool used to create the resource. If history is " + "present in the metadata, this value should be equivalent to that of " + "xmpMM:History's softwareAgent property." }, + { "Identifier", "Identifier", "bag Text", xmpText, xmpExternal, "An unordered array of text strings that unambiguously identify the resource within " + "a given context. An array item may be qualified with xmpidq:Scheme to denote the " + "formal identification system to which that identifier conforms. Note: The " + "dc:identifier property is not used because it lacks a defined scheme qualifier and " + "has been defined in the XMP Specification as a simple (single-valued) property." }, + { "Label", "Label", "Text", xmpText, xmpExternal, "A word or short phrase that identifies a document as a member of a user-defined " + "collection. Used to organize documents in a file browser." }, + { "MetadataDate", "MetadataDate", "Date", xmpText, xmpInternal, "The date and time that any metadata for this resource was last changed. It should " + "be the same as or more recent than xmp:ModifyDate." }, + { "ModifyDate", "ModifyDate", "Date", xmpText, xmpInternal, "The date and time the resource was last modified. Note: The value of this property " + "is not necessarily the same as the file's system modification date because it is " + "set before the file is saved." }, + { "Nickname", "Nickname", "Text", xmpText, xmpExternal, "A short informal name for the resource." }, + { "Rating", "Rating", "Closed Choice of Integer", signedLong, xmpExternal, "A number that indicates a document's status relative to other documents, " + "used to organize documents in a file browser. Values are user-defined within an " + "application-defined range." }, + { "Thumbnails", "Thumbnails", "alt Thumbnail", undefined, xmpInternal, "An alternative array of thumbnail images for a file, which can differ in " + "characteristics such as size or image encoding." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + extern const XmpPropertyInfo xmpXmpRightsInfo[] = { + { "Certificate", "Certificate", "URL", xmpText, xmpExternal, "Online rights management certificate." }, + { "Marked", "Marked", "Boolean", xmpText, xmpExternal, "Indicates that this is a rights-managed resource." }, + { "Owner", "Owner", "bag ProperName", xmpText, xmpExternal, "An unordered array specifying the legal owner(s) of a resource." }, + { "UsageTerms", "UsageTerms", "Lang Alt", xmpText, xmpExternal, "Text instructions on how a resource can be legally used." }, + { "WebStatement", "WebStatement", "URL", xmpText, xmpExternal, "The location of a web page describing the owner and/or rights statement for this resource." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + extern const XmpPropertyInfo xmpXmpMMInfo[] = { + { "DerivedFrom", "DerivedFrom", "ResourceRef", xmpText, xmpInternal, "A reference to the original document from which this one is derived. It is a " + "minimal reference; missing components can be assumed to be unchanged. For example, " + "a new version might only need to specify the instance ID and version number of the " + "previous version, or a rendition might only need to specify the instance ID and " + "rendition class of the original." }, + { "DocumentID", "DocumentID", "URI", xmpText, xmpInternal, "The common identifier for all versions and renditions of a document. It should be " + "based on a UUID; see Document and Instance IDs below." }, + { "History", "History", "seq ResourceEvent", xmpText, xmpInternal, "An ordered array of high-level user actions that resulted in this resource. It is " + "intended to give human readers a general indication of the steps taken to make the " + "changes from the previous version to this one. The list should be at an abstract " + "level; it is not intended to be an exhaustive keystroke or other detailed history." }, + { "InstanceID", "InstanceID", "URI", xmpText, xmpInternal, "An identifier for a specific incarnation of a document, updated each time a file " + "is saved. It should be based on a UUID; see Document and Instance IDs below." }, + { "ManagedFrom", "ManagedFrom", "ResourceRef", xmpText, xmpInternal, "A reference to the document as it was prior to becoming managed. It is set when a " + "managed document is introduced to an asset management system that does not " + "currently own it. It may or may not include references to different management systems." }, + { "Manager", "Manager", "AgentName", xmpText, xmpInternal, "The name of the asset management system that manages this resource. Along with " + "xmpMM: ManagerVariant, it tells applications which asset management system to " + "contact concerning this document." }, + { "ManageTo", "ManageTo", "URI", xmpText, xmpInternal, "A URI identifying the managed resource to the asset management system; the presence " + "of this property is the formal indication that this resource is managed. The form " + "and content of this URI is private to the asset management system." }, + { "ManageUI", "ManageUI", "URI", xmpText, xmpInternal, "A URI that can be used to access information about the managed resource through a " + "web browser. It might require a custom browser plug- in." }, + { "ManagerVariant", "ManagerVariant", "Text", xmpText, xmpInternal, "Specifies a particular variant of the asset management system. The format of this " + "property is private to the specific asset management system." }, + { "RenditionClass", "RenditionClass", "RenditionClass", xmpText, xmpInternal, "The rendition class name for this resource. This property should be absent or set " + "to default for a document version that is not a derived rendition." }, + { "RenditionParams", "RenditionParams", "Text", xmpText, xmpInternal, "Can be used to provide additional rendition parameters that are too complex or " + "verbose to encode in xmpMM: RenditionClass." }, + { "VersionID", "VersionID", "Text", xmpText, xmpInternal, "The document version identifier for this resource. Each version of a document gets " + "a new identifier, usually simply by incrementing integers 1, 2, 3 . . . and so on. " + "Media management systems can have other conventions or support branching which " + "requires a more complex scheme." }, + { "Versions", "Versions", "seq Version", xmpText, xmpInternal, "The version history associated with this resource. Entry [1] is the oldest known " + "version for this document, entry [last()] is the most recent version. Typically, a " + "media management system would fill in the version information in the metadata on " + "check-in. It is not guaranteed that a complete history versions from the first to " + "this one will be present in the xmpMM:Versions property. Interior version information " + "can be compressed or eliminated and the version history can be truncated at some point." }, + { "LastURL", "LastURL", "URL", xmpText, xmpInternal, "Deprecated for privacy protection." }, + { "RenditionOf", "RenditionOf", "ResourceRef", xmpText, xmpInternal, "Deprecated in favor of xmpMM:DerivedFrom. A reference to the document of which this is " + "a rendition." }, + { "SaveID", "SaveID", "Integer", signedLong, xmpInternal, "Deprecated. Previously used only to support the xmpMM:LastURL property." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + extern const XmpPropertyInfo xmpXmpBJInfo[] = { + { "JobRef", "JobRef", "bag Job", xmpText, xmpExternal, "References an external job management file for a job process in which the document is being used. Use of job " + "names is under user control. Typical use would be to identify all documents that are part of a particular job or contract. " + "There are multiple values because there can be more than one job using a particular document at any time, and it can " + "also be useful to keep historical information about what jobs a document was part of previously." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + extern const XmpPropertyInfo xmpXmpTPgInfo[] = { + { "MaxPageSize", "MaxPageSize", "Dimensions", xmpText, xmpInternal, "The size of the largest page in the document (including any in contained documents)." }, + { "NPages", "NPages", "Integer", unsignedLong, xmpInternal, "The number of pages in the document (including any in contained documents)." }, + { "Fonts", "Fonts", "bag Font", xmpText, xmpInternal, "An unordered array of fonts that are used in the document (including any in contained documents)." }, + { "Colorants", "Colorants", "seq Colorant", xmpText, xmpInternal, "An ordered array of colorants (swatches) that are used in the document (including any in contained documents)." }, + { "PlateNames", "PlateNames", "seq Text", xmpText, xmpInternal, "An ordered array of plate names that are needed to print the document (including any in contained documents)." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + extern const XmpPropertyInfo xmpXmpDMInfo[] = { + { "projectRef", "projectRef", "ProjectLink", xmpText, xmpInternal, "A reference to the project that created this file." }, + { "videoFrameRate", "videoFrameRate", "open Choice of Text", xmpText, xmpInternal, "The video frame rate. One of: 24, NTSC, PAL." }, + { "videoFrameSize", "videoFrameSize", "Dimensions", xmpText, xmpInternal, "The frame size. For example: w:720, h: 480, unit:pixels" }, + { "videoPixelAspectRatio", "videoPixelAspectRatio", "Rational", unsignedRational, xmpInternal, "The aspect ratio, expressed as ht/wd. For example: \"648/720\" = 0.9" }, + { "videoPixelDepth", "videoPixelDepth", "closed Choice of Text", xmpText, xmpInternal, "The size in bits of each color component of a pixel. Standard Windows 32-bit " + "pixels have 8 bits per component. One of: 8Int, 16Int, 32Int, 32Float." }, + { "videoColorSpace", "videoColorSpace", "closed Choice of Text", xmpText, xmpInternal, "The color space. One of: sRGB (used by Photoshop), CCIR-601 (used for NTSC), " + "CCIR-709 (used for HD)." }, + { "videoAlphaMode", "videoAlphaMode", "closed Choice of Text", xmpText, xmpExternal, "The alpha mode. One of: straight, pre-multiplied." }, + { "videoAlphaPremultipleColor", "videoAlphaPremultipleColor", "Colorant", xmpText, xmpExternal, "A color in CMYK or RGB to be used as the pre-multiple color when " + "alpha mode is pre-multiplied." }, + { "videoAlphaUnityIsTransparent", "videoAlphaUnityIsTransparent", "Boolean", xmpText, xmpInternal, "When true, unity is clear, when false, it is opaque." }, + { "videoCompressor", "videoCompressor", "Text", xmpText, xmpInternal, "Video compression used. For example, jpeg." }, + { "videoFieldOrder", "videoFieldOrder", "closed Choice of Text", xmpText, xmpInternal, "The field order for video. One of: Upper, Lower, Progressive." }, + { "pullDown", "pullDown", "closed Choice of Text", xmpText, xmpInternal, "The sampling phase of film to be converted to video (pull-down). One of: " + "WSSWW, SSWWW, SWWWS, WWWSS, WWSSW, WSSWW_24p, SSWWW_24p, SWWWS_24p, WWWSS_24p, WWSSW_24p." }, + { "audioSampleRate", "audioSampleRate", "Integer", unsignedLong, xmpInternal, "The audio sample rate. Can be any value, but commonly 32000, 41100, or 48000." }, + { "audioSampleType", "audioSampleType", "closed Choice of Text", xmpText, xmpInternal, "The audio sample type. One of: 8Int, 16Int, 32Int, 32Float." }, + { "audioChannelType", "audioChannelType", "closed Choice of Text", xmpText, xmpInternal, "The audio channel type. One of: Mono, Stereo, 5.1, 7.1." }, + { "audioCompressor", "audioCompressor", "Text", xmpText, xmpInternal, "The audio compression used. For example, MP3." }, + { "speakerPlacement", "speakerPlacement", "Text", xmpText, xmpExternal, "A description of the speaker angles from center front in degrees. For example: " + "\"Left = -30, Right = 30, Center = 0, LFE = 45, Left Surround = -110, Right Surround = 110\"" }, + { "fileDataRate", "fileDataRate", "Rational", unsignedRational, xmpInternal, "The file data rate in megabytes per second. For example: \"36/10\" = 3.6 MB/sec" }, + { "tapeName", "tapeName", "Text", xmpText, xmpExternal, "The name of the tape from which the clip was captured, as set during the capture process." }, + { "altTapeName", "altTapeName", "Text", xmpText, xmpExternal, "An alternative tape name, set via the project window or timecode dialog in Premiere. " + "If an alternative name has been set and has not been reverted, that name is displayed." }, + { "startTimecode", "startTimecode", "Timecode", xmpText, xmpInternal, "The timecode of the first frame of video in the file, as obtained from the device control." }, + { "altTimecode", "altTimecode", "Timecode", xmpText, xmpExternal, "A timecode set by the user. When specified, it is used instead of the startTimecode." }, + { "duration", "duration", "Time", xmpText, xmpInternal, "The duration of the media file." }, + { "scene", "scene", "Text", xmpText, xmpExternal, "The name of the scene." }, + { "shotName", "shotName", "Text", xmpText, xmpExternal, "The name of the shot or take." }, + { "shotDate", "shotDate", "Date", xmpText, xmpExternal, "The date and time when the video was shot." }, + { "shotLocation", "shotLocation", "Text", xmpText, xmpExternal, "The name of the location where the video was shot. For example: \"Oktoberfest, Munich Germany\" " + "For more accurate positioning, use the EXIF GPS values." }, + { "logComment", "logComment", "Text", xmpText, xmpExternal, "User's log comments." }, + { "markers", "markers", "seq Marker", xmpText, xmpInternal, "An ordered list of markers" }, + { "contributedMedia", "contributedMedia", "bag Media", xmpText, xmpInternal, "An unordered list of all media used to create this media." }, + { "absPeakAudioFilePath", "absPeakAudioFilePath", "URI", xmpText, xmpInternal, "The absolute path to the file's peak audio file. If empty, no peak file exists." }, + { "relativePeakAudioFilePath", "relativePeakAudioFilePath", "URI", xmpText, xmpInternal, "The relative path to the file's peak audio file. If empty, no peak file exists." }, + { "videoModDate", "videoModDate", "Date", xmpText, xmpInternal, "The date and time when the video was last modified." }, + { "audioModDate", "audioModDate", "Date", xmpText, xmpInternal, "The date and time when the audio was last modified." }, + { "metadataModDate", "metadataModDate", "Date", xmpText, xmpInternal, "The date and time when the metadata was last modified." }, + { "artist", "artist", "Text", xmpText, xmpExternal, "The name of the artist or artists." }, + { "album", "album", "Text", xmpText, xmpExternal, "The name of the album." }, + { "trackNumber", "trackNumber", "Integer", unsignedShort, xmpExternal, "A numeric value indicating the order of the audio file within its original recording." }, + { "genre", "genre", "Text", xmpText, xmpExternal, "The name of the genre." }, + { "copyright", "copyright", "Text", xmpText, xmpExternal, "The copyright information." }, + { "releaseDate", "releaseDate", "Date", xmpText, xmpExternal, "The date the title was released." }, + { "composer", "composer", "Text", xmpText, xmpExternal, "The composer's name." }, + { "engineer", "engineer", "Text", xmpText, xmpExternal, "The engineer's name." }, + { "tempo", "tempo", "Real", xmpText, xmpInternal, "The audio's tempo." }, + { "instrument", "instrument", "Text", xmpText, xmpExternal, "The musical instrument." }, + { "introTime", "introTime", "Time", xmpText, xmpInternal, "The duration of lead time for queuing music." }, + { "outCue", "outCue", "Time", xmpText, xmpInternal, "The time at which to fade out." }, + { "relativeTimestamp", "relativeTimestamp", "Time", xmpText, xmpInternal, "The start time of the media inside the audio project." }, + { "loop", "loop", "Boolean", xmpText, xmpInternal, "When true, the clip can be looped seemlessly." }, + { "numberOfBeats", "numberOfBeats", "Real", xmpText, xmpInternal, "The number of beats." }, + { "key", "key", "closed Choice of Text", xmpText, xmpInternal, "The audio's musical key. One of: C, C#, D, D#, E, F, F#, G, G#, A, A#, B." }, + { "stretchMode", "stretchMode", "closed Choice of Text", xmpText, xmpInternal, "The audio stretch mode. One of: Fixed length, Time-Scale, Resample, Beat Splice, Hybrid." }, + { "timeScaleParams", "timeScaleParams", "timeScaleStretch", xmpText, xmpInternal, "Additional parameters for Time-Scale stretch mode." }, + { "resampleParams", "resampleParams", "resampleStretch", xmpText, xmpInternal, "Additional parameters for Resample stretch mode." }, + { "beatSpliceParams", "beatSpliceParams", "beatSpliceStretch", xmpText, xmpInternal, "Additional parameters for Beat Splice stretch mode." }, + { "timeSignature", "timeSignature", "closed Choice of Text", xmpText, xmpInternal, "The time signature of the music. One of: 2/4, 3/4, 4/4, 5/4, 7/4, 6/8, 9/8, 12/8, other." }, + { "scaleType", "scaleType", "closed Choice of Text", xmpText, xmpInternal, "The musical scale used in the music. One of: Major, Minor, Both, Neither. " + "Neither is most often used for instruments with no associated scale, such as drums." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + extern const XmpPropertyInfo xmpPdfInfo[] = { + { "Keywords", "Keywords", "Text", xmpText, xmpExternal, "Keywords." }, + { "PDFVersion", "PDFVersion", "Text", xmpText, xmpInternal, "The PDF file version (for example: 1.0, 1.3, and so on)." }, + { "Producer", "Producer", "AgentName", xmpText, xmpInternal, "The name of the tool that created the PDF document." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + extern const XmpPropertyInfo xmpPhotoshopInfo[] = { + { "AuthorsPosition", "AuthorsPosition", "Text", xmpText, xmpExternal, "By-line title." }, + { "CaptionWriter", "CaptionWriter", "ProperName", xmpText, xmpExternal, "Writer/editor." }, + { "Category", "Category", "Text", xmpText, xmpExternal, "Category. Limited to 3 7-bit ASCII characters." }, + { "City", "City", "Text", xmpText, xmpExternal, "City." }, + { "Country", "Country", "Text", xmpText, xmpExternal, "Country/primary location." }, + { "Credit", "Credit", "Text", xmpText, xmpExternal, "Credit." }, + { "DateCreated", "DateCreated", "Date", xmpText, xmpExternal, "The date the intellectual content of the document was created (rather than the creation " + "date of the physical representation), following IIM conventions. For example, a photo " + "taken during the American Civil War would have a creation date during that epoch " + "(1861-1865) rather than the date the photo was digitized for archiving." }, + { "Headline", "Headline", "Text", xmpText, xmpExternal, "Headline." }, + { "Instructions", "Instructions", "Text", xmpText, xmpExternal, "Special instructions." }, + { "Source", "Source", "Text", xmpText, xmpExternal, "Source." }, + { "State", "State", "Text", xmpText, xmpExternal, "Province/state." }, + { "SupplementalCategories", "SupplementalCategories", "bag Text", xmpText, xmpExternal, "Supplemental category." }, + { "TransmissionReference", "TransmissionReference", "Text", xmpText, xmpExternal, "Original transmission reference." }, + { "Urgency", "Urgency", "Integer", xmpText, xmpExternal, "Urgency. Valid range is 1-8." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + extern const XmpPropertyInfo xmpTiffInfo[] = { + { "ImageWidth", "ImageWidth", "Integer", unsignedLong, xmpInternal, "TIFF tag 256, 0x100. Image width in pixels." }, + { "ImageLength", "ImageLength", "Integer", unsignedLong, xmpInternal, "TIFF tag 257, 0x101. Image height in pixels." }, + { "BitsPerSample", "BitsPerSample", "seq Integer", unsignedShort, xmpInternal, "TIFF tag 258, 0x102. Number of bits per component in each channel." }, + { "Compression", "Compression", "Closed Choice of Integer", unsignedShort, xmpInternal, "TIFF tag 259, 0x103. Compression scheme: 1 = uncompressed; 6 = JPEG." }, + { "PhotometricInterpretation", "PhotometricInterpretation", "Closed Choice of Integer", unsignedShort, xmpInternal, "TIFF tag 262, 0x106. Pixel Composition: 2 = RGB; 6 = YCbCr." }, + { "Orientation", "Orientation", "Closed Choice of Integer", unsignedShort, xmpInternal, "TIFF tag 274, 0x112. Orientation:" + "1 = 0th row at top, 0th column at left " + "2 = 0th row at top, 0th column at right " + "3 = 0th row at bottom, 0th column at right " + "4 = 0th row at bottom, 0th column at left " + "5 = 0th row at left, 0th column at top " + "6 = 0th row at right, 0th column at top " + "7 = 0th row at right, 0th column at bottom " + "8 = 0th row at left, 0th column at bottom" }, + { "SamplesPerPixel", "SamplesPerPixel", "Integer", unsignedShort, xmpInternal, "TIFF tag 277, 0x115. Number of components per pixel." }, + { "PlanarConfiguration", "PlanarConfiguration", "Closed Choice of Integer", unsignedShort, xmpInternal, "TIFF tag 284, 0x11C. Data layout:1 = chunky; 2 = planar." }, + { "YCbCrSubSampling", "YCbCrSubSampling", "Closed Choice of seq Integer", unsignedShort, xmpInternal, "TIFF tag 530, 0x212. Sampling ratio of chrominance " + "components: [2, 1] = YCbCr4:2:2; [2, 2] = YCbCr4:2:0" }, + { "YCbCrPositioning", "YCbCrPositioning", "Closed Choice of Integer", unsignedShort, xmpInternal, "TIFF tag 531, 0x213. Position of chrominance vs. " + "luminance components: 1 = centered; 2 = co-sited." }, + { "XResolution", "XResolution", "Rational", unsignedRational, xmpInternal, "TIFF tag 282, 0x11A. Horizontal resolution in pixels per unit." }, + { "YResolution", "YResolution", "Rational", unsignedRational, xmpInternal, "TIFF tag 283, 0x11B. Vertical resolution in pixels per unit." }, + { "ResolutionUnit", "ResolutionUnit", "Closed Choice of Integer", unsignedShort, xmpInternal, "TIFF tag 296, 0x128. Unit used for XResolution and " + "YResolution. Value is one of: 2 = inches; 3 = centimeters." }, + { "TransferFunction", "TransferFunction", "seq Integer", unsignedShort, xmpInternal, "TIFF tag 301, 0x12D. Transfer function for image " + "described in tabular style with 3 * 256 entries." }, + { "WhitePoint", "WhitePoint", "seq Rational", unsignedRational, xmpInternal, "TIFF tag 318, 0x13E. Chromaticity of white point." }, + { "PrimaryChromaticities", "PrimaryChromaticities", "seq Rational", unsignedRational, xmpInternal, "TIFF tag 319, 0x13F. Chromaticity of the three primary colors." }, + { "YCbCrCoefficients", "YCbCrCoefficients", "seq Rational", unsignedRational, xmpInternal, "TIFF tag 529, 0x211. Matrix coefficients for RGB to YCbCr transformation." }, + { "ReferenceBlackWhite", "ReferenceBlackWhite", "seq Rational", unsignedRational, xmpInternal, "TIFF tag 532, 0x214. Reference black and white point values." }, + { "DateTime", "DateTime", "Date", date, xmpInternal, "TIFF tag 306, 0x132 (primary) and EXIF tag 37520, " + "0x9290 (subseconds). Date and time of image creation " + "(no time zone in EXIF), stored in ISO 8601 format, not " + "the original EXIF format. This property includes the " + "value for the EXIF SubSecTime attribute. " + "NOTE: This property is stored in XMP as xmp:ModifyDate." }, + { "ImageDescription", "ImageDescription", "Lang Alt", xmpText, xmpExternal, "TIFF tag 270, 0x10E. Description of the image. Note: This property is stored in XMP as dc:description." }, + { "Make", "Make", "ProperName", xmpText, xmpInternal, "TIFF tag 271, 0x10F. Manufacturer of recording equipment." }, + { "Model", "Model", "ProperName", xmpText, xmpInternal, "TIFF tag 272, 0x110. Model name or number of equipment." }, + { "Software", "Software", "AgentName", xmpText, xmpInternal, "TIFF tag 305, 0x131. Software or firmware used to generate image. " + "Note: This property is stored in XMP as xmp:CreatorTool. " }, + { "Artist", "Artist", "ProperName", xmpText, xmpExternal, "TIFF tag 315, 0x13B. Camera owner, photographer or image creator. " + "Note: This property is stored in XMP as the first item in the dc:creator array." }, + { "Copyright", "Copyright", "Lang Alt", xmpText, xmpExternal, "TIFF tag 33432, 0x8298. Copyright information. " + "Note: This property is stored in XMP as dc:rights." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + //! exif:ColorSpace + extern const TagDetails xmpExifColorSpace[] = { + { 1, N_("sRGB") }, + { 65535, N_("uncalibrated") } + }; + + //! exif:ComponentsConfiguration + extern const TagDetails xmpExifComponentsConfiguration[] = { + { 0, "does not exist" }, + { 1, "Y" }, + { 2, "Cb" }, + { 3, "Cr" }, + { 4, "R" }, + { 5, "G" }, + { 6, "B" } + }; + + //! exif:ExposureProgram + extern const TagDetails xmpExifExposureProgram[] = { + { 0, N_("not defined") }, + { 1, N_("Manual") }, + { 2, N_("Normal program") }, + { 3, N_("Aperture priority") }, + { 4, N_("Shutter priority") }, + { 5, N_("Creative program") }, + { 6, N_("Action program") }, + { 7, N_("Portrait mode") }, + { 8, N_("Landscape mode") } + }; + + //! exif:MeteringMode + extern const TagDetails xmpExifMeteringMode[] = { + { 0, N_("unknown") }, + { 1, N_("Average") }, + { 2, N_("CenterWeightedAverage") }, + { 3, N_("Spot") }, + { 4, N_("MultiSpot") }, + { 5, N_("Pattern") }, + { 6, N_("Partial") }, + { 255, N_("other") } + }; + + //! exif:LightSource + extern const TagDetails xmpExifLightSource[] = { + { 0, N_("unknown") }, + { 1, N_("Daylight") }, + { 2, N_("Fluorescent") }, + { 3, N_("Tungsten") }, + { 4, N_("Flash") }, + { 9, N_("Fine weather") }, + { 10, N_("Cloudy weather") }, + { 11, N_("Shade") }, + { 12, N_("Daylight fluorescent (D 5700 - 7100K)") }, + { 13, N_("Day white fluorescent (N 4600 - 5400K)") }, + { 14, N_("Cool white fluorescent (W 3900 - 4500K)") }, + { 15, N_("White fluorescent (WW 3200 - 3700K)") }, + { 17, N_("Standard light A") }, + { 18, N_("Standard light B") }, + { 19, N_("Standard light C") }, + { 20, N_("D55") }, + { 21, N_("D65") }, + { 22, N_("D75") }, + { 23, N_("D50") }, + { 24, N_("ISO studio tungsten") }, + { 255, N_("other") } + }; + + //! exif:FocalPlaneResolutionUnit + extern const TagDetails xmpExifFocalPlaneResolutionUnit[] = { + { 2, N_("inches") }, + { 3, N_("centimeters") } + }; + + //! exif:SensingMethod + extern const TagDetails xmpExifSensingMethod[] = { + { 1, N_("Not defined") }, + { 2, N_("One-chip color area sensor") }, + { 3, N_("Two-chip color area sensor") }, + { 4, N_("Three-chip color area sensor") }, + { 5, N_("Color sequential area sensor") }, + { 7, N_("Trilinear sensor") }, + { 8, N_("Color sequential linear sensor") } + }; + + //! exif:FileSource + extern const TagDetails xmpExifFileSource[] = { + { 3, N_("DSC") } + }; + + //! exif:SceneType + extern const TagDetails xmpExifSceneType[] = { + { 1, N_("directly photographed image") } + }; + + //! exif:CustomRendered + extern const TagDetails xmpExifCustomRendered[] = { + { 0, N_("Normal process") }, + { 1, N_("Custom process") } + }; + + //! exif:ExposureMode + extern const TagDetails xmpExifExposureMode[] = { + { 0, N_("Auto exposure") }, + { 1, N_("Manual exposure") }, + { 2, N_("Auto bracket") } + }; + + //! exif:WhiteBalance + extern const TagDetails xmpExifWhiteBalance[] = { + { 0, N_("Auto white balance") }, + { 1, N_("Manual white balance") } + }; + + //! exif:SceneCaptureType + extern const TagDetails xmpExifSceneCaptureType[] = { + { 0, N_("Standard") }, + { 1, N_("Landscape") }, + { 2, N_("Portrait") }, + { 3, N_("Night scene") } + }; + + //! exif:GainControl + extern const TagDetails xmpExifGainControl[] = { + { 0, N_("None") }, + { 1, N_("Low gain up") }, + { 2, N_("High gain up") }, + { 3, N_("Low gain down") }, + { 4, N_("High gain down") } + }; + + //! exif:Contrast, exif:Sharpness + extern const TagDetails xmpExifNormalSoftHard[] = { + { 0, N_("Normal") }, + { 1, N_("Soft") }, + { 2, N_("Hard") } + }; + + //! exif:Saturation + extern const TagDetails xmpExifSaturation[] = { + { 0, N_("Normal") }, + { 1, N_("Low saturation") }, + { 2, N_("High saturation") } + }; + + //! exif:SubjectDistanceRange + extern const TagDetails xmpExifSubjectDistanceRange[] = { + { 0, N_("Unknown") }, + { 1, N_("Macro") }, + { 2, N_("Close view") }, + { 3, N_("Distant view") } + }; + + //! exif:GPSAltitudeRef + extern const TagDetails xmpExifGPSAltitudeRef[] = { + { 0, N_("Above sea level") }, + { 1, N_("Below sea level") } + }; + + //! exif:GPSStatus + extern const TagDetails xmpExifGPSStatus[] = { + { 'A', N_("measurement in progress") }, + { 'V', N_("measurement is interoperability") } + }; + + //! exif:GPSMeasureMode + extern const TagDetails xmpExifGPSMeasureMode[] = { + { 2, N_("two-dimensional measurement") }, + { 3, N_("three-dimensional measurement") } + }; + + //! exif:GPSSpeedRef + extern const TagDetails xmpExifGPSSpeedRef[] = { + { 'K', N_("kilometers per hour") }, + { 'M', N_("miles per hour") }, + { 'N', N_("knots") } + }; + + //! exif:GPSTrackRef, exif:GPSImgDirectionRef, exif:GPSDestBearingRef + extern const TagDetails xmpExifGPSDirection[] = { + { 'T', N_("true direction") }, + { 'M', N_("magnetic direction") } + }; + + //! exif:GPSDestDistanceRef + extern const TagDetails xmpExifGPSDestDistanceRef[] = { + { 'K', N_("kilometers") }, + { 'M', N_("miles") }, + { 'N', N_("knots") } + }; + + //! exif:GPSDifferential + extern const TagDetails xmpExifGPSDifferential[] = { + { 0, N_("Without correction") }, + { 1, N_("Correction applied") } + }; + + extern const XmpPropertyInfo xmpExifInfo[] = { + { "ExifVersion", "ExifVersion", "Closed Choice of Text", xmpText, xmpInternal, "EXIF tag 36864, 0x9000. EXIF version number." }, + { "FlashpixVersion", "FlashpixVersion", "Closed Choice of Text", xmpText, xmpInternal, "EXIF tag 40960, 0xA000. Version of FlashPix." }, + { "ColorSpace", "ColorSpace", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 40961, 0xA001. Color space information" }, + { "ComponentsConfiguration", "ComponentsConfiguration", "Closed Choice of seq Integer", unsignedShort, xmpInternal, "EXIF tag 37121, 0x9101. Configuration of components in data: 4 5 6 0 (if RGB compressed data), 1 2 3 0 (other cases)." }, + { "CompressedBitsPerPixel", "CompressedBitsPerPixel", "Rational", unsignedRational, xmpInternal, "EXIF tag 37122, 0x9102. Compression mode used for a compressed image is indicated in unit bits per pixel." }, + { "PixelXDimension", "PixelXDimension", "Integer", unsignedLong, xmpInternal, "EXIF tag 40962, 0xA002. Valid image width, in pixels." }, + { "PixelYDimension", "PixelYDimension", "Integer", unsignedLong, xmpInternal, "EXIF tag 40963, 0xA003. Valid image height, in pixels." }, + { "UserComment", "UserComment", "Lang Alt", xmpText, xmpExternal, "EXIF tag 37510, 0x9286. Comments from user." }, + { "RelatedSoundFile", "RelatedSoundFile", "Text", xmpText, xmpInternal, "EXIF tag 40964, 0xA004. An \"8.3\" file name for the related sound file." }, + { "DateTimeOriginal", "DateTimeOriginal", "Date", xmpText, xmpInternal, "EXIF tags 36867, 0x9003 (primary) and 37521, 0x9291 (subseconds). Date and time when original image was generated, in ISO 8601 format. Includes the EXIF SubSecTimeOriginal data." }, + { "DateTimeDigitized", "DateTimeDigitized", "Date", xmpText, xmpInternal, "EXIF tag 36868, 0x9004 (primary) and 37522, 0x9292 (subseconds). Date and time when " + "image was stored as digital data, can be the same as DateTimeOriginal if originally " + "stored in digital form. Stored in ISO 8601 format. Includes the EXIF SubSecTimeDigitized data." }, + { "ExposureTime", "ExposureTime", "Rational", unsignedRational, xmpInternal, "EXIF tag 33434, 0x829A. Exposure time in seconds." }, + { "FNumber", "FNumber", "Rational", unsignedRational, xmpInternal, "EXIF tag 33437, 0x829D. F number." }, + { "ExposureProgram", "ExposureProgram", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 34850, 0x8822. Class of program used for exposure." }, + { "SpectralSensitivity", "SpectralSensitivity", "Text", xmpText, xmpInternal, "EXIF tag 34852, 0x8824. Spectral sensitivity of each channel." }, + { "ISOSpeedRatings", "ISOSpeedRatings", "seq Integer", unsignedShort, xmpInternal, "EXIF tag 34855, 0x8827. ISO Speed and ISO Latitude of the input device as specified in ISO 12232." }, + { "OECF", "OECF", "OECF/SFR", xmpText, xmpInternal, "EXIF tag 34856, 0x8828. Opto-Electoric Conversion Function as specified in ISO 14524." }, + { "ShutterSpeedValue", "ShutterSpeedValue", "Rational", signedRational, xmpInternal, "EXIF tag 37377, 0x9201. Shutter speed, unit is APEX. See Annex C of the EXIF specification." }, + { "ApertureValue", "ApertureValue", "Rational", unsignedRational, xmpInternal, "EXIF tag 37378, 0x9202. Lens aperture, unit is APEX." }, + { "BrightnessValue", "BrightnessValue", "Rational", signedRational, xmpInternal, "EXIF tag 37379, 0x9203. Brightness, unit is APEX." }, + { "ExposureBiasValue", "ExposureBiasValue", "Rational", signedRational, xmpInternal, "EXIF tag 37380, 0x9204. Exposure bias, unit is APEX." }, + { "MaxApertureValue", "MaxApertureValue", "Rational", unsignedRational, xmpInternal, "EXIF tag 37381, 0x9205. Smallest F number of lens, in APEX." }, + { "SubjectDistance", "SubjectDistance", "Rational", unsignedRational, xmpInternal, "EXIF tag 37382, 0x9206. Distance to subject, in meters." }, + { "MeteringMode", "MeteringMode", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 37383, 0x9207. Metering mode." }, + { "LightSource", "LightSource", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 37384, 0x9208. Light source." }, + { "Flash", "Flash", "Flash", xmpText, xmpInternal, "EXIF tag 37385, 0x9209. Strobe light (flash) source data." }, + { "FocalLength", "FocalLength", "Rational", unsignedRational, xmpInternal, "EXIF tag 37386, 0x920A. Focal length of the lens, in millimeters." }, + { "SubjectArea", "SubjectArea", "seq Integer", unsignedShort, xmpInternal, "EXIF tag 37396, 0x9214. The location and area of the main subject in the overall scene." }, + { "FlashEnergy", "FlashEnergy", "Rational", unsignedRational, xmpInternal, "EXIF tag 41483, 0xA20B. Strobe energy during image capture." }, + { "SpatialFrequencyResponse", "SpatialFrequencyResponse", "OECF/SFR", xmpText, xmpInternal, "EXIF tag 41484, 0xA20C. Input device spatial frequency table and SFR values as specified in ISO 12233." }, + { "FocalPlaneXResolution", "FocalPlaneXResolution", "Rational", unsignedRational, xmpInternal, "EXIF tag 41486, 0xA20E. Horizontal focal resolution, measured pixels per unit." }, + { "FocalPlaneYResolution", "FocalPlaneYResolution", "Rational", unsignedRational, xmpInternal, "EXIF tag 41487, 0xA20F. Vertical focal resolution, measured in pixels per unit." }, + { "FocalPlaneResolutionUnit", "FocalPlaneResolutionUnit", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41488, 0xA210. Unit used for FocalPlaneXResolution and FocalPlaneYResolution." }, + { "SubjectLocation", "SubjectLocation", "seq Integer", unsignedShort, xmpInternal, "EXIF tag 41492, 0xA214. Location of the main subject of the scene. The first value is the " + "horizontal pixel and the second value is the vertical pixel at which the main subject appears." }, + { "ExposureIndex", "ExposureIndex", "Rational", unsignedRational, xmpInternal, "EXIF tag 41493, 0xA215. Exposure index of input device." }, + { "SensingMethod", "SensingMethod", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41495, 0xA217. Image sensor type on input device." }, + { "FileSource", "FileSource", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41728, 0xA300. Indicates image source." }, + { "SceneType", "SceneType", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41729, 0xA301. Indicates the type of scene." }, + { "CFAPattern", "CFAPattern", "CFAPattern", xmpText, xmpInternal, "EXIF tag 41730, 0xA302. Color filter array geometric pattern of the image sense." }, + { "CustomRendered", "CustomRendered", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41985, 0xA401. Indicates the use of special processing on image data." }, + { "ExposureMode", "ExposureMode", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41986, 0xA402. Indicates the exposure mode set when the image was shot." }, + { "WhiteBalance", "WhiteBalance", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41987, 0xA403. Indicates the white balance mode set when the image was shot." }, + { "DigitalZoomRatio", "DigitalZoomRatio", "Rational", unsignedRational, xmpInternal, "EXIF tag 41988, 0xA404. Indicates the digital zoom ratio when the image was shot." }, + { "FocalLengthIn35mmFilm", "FocalLengthIn35mmFilm", "Integer", unsignedShort, xmpInternal, "EXIF tag 41989, 0xA405. Indicates the equivalent focal length assuming a 35mm film " + "camera, in mm. A value of 0 means the focal length is unknown. Note that this tag differs from the FocalLength tag." }, + { "SceneCaptureType", "SceneCaptureType", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41990, 0xA406. Indicates the type of scene that was shot." }, + { "GainControl", "GainControl", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41991, 0xA407. Indicates the degree of overall image gain adjustment." }, + { "Contrast", "Contrast", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41992, 0xA408. Indicates the direction of contrast processing applied by the camera." }, + { "Saturation", "Saturation", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41993, 0xA409. Indicates the direction of saturation processing applied by the camera." }, + { "Sharpness", "Sharpness", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41994, 0xA40A. Indicates the direction of sharpness processing applied by the camera." }, + { "DeviceSettingDescription", "DeviceSettingDescription", "DeviceSettings", xmpText, xmpInternal, "EXIF tag 41995, 0xA40B. Indicates information on the picture-taking conditions of a particular camera model." }, + { "SubjectDistanceRange", "SubjectDistanceRange", "Closed Choice of Integer", unsignedShort, xmpInternal, "EXIF tag 41996, 0xA40C. Indicates the distance to the subject." }, + { "ImageUniqueID", "ImageUniqueID", "Text", xmpText, xmpInternal, "EXIF tag 42016, 0xA420. An identifier assigned uniquely to each image. It is recorded as a 32 " + "character ASCII string, equivalent to hexadecimal notation and 128-bit fixed length." }, + { "GPSVersionID", "GPSVersionID", "Text", xmpText, xmpInternal, "GPS tag 0, 0x00. A decimal encoding of each of the four EXIF bytes with period separators. The current value is \"2.0.0.0\"." }, + { "GPSLatitude", "GPSLatitude", "GPSCoordinate", xmpText, xmpInternal, "GPS tag 2, 0x02 (position) and 1, 0x01 (North/South). Indicates latitude." }, + { "GPSLongitude", "GPSLongitude", "GPSCoordinate", xmpText, xmpInternal, "GPS tag 4, 0x04 (position) and 3, 0x03 (East/West). Indicates longitude." }, + { "GPSAltitudeRef", "GPSAltitudeRef", "Closed Choice of Integer", unsignedByte, xmpInternal, "GPS tag 5, 0x5. Indicates whether the altitude is above or below sea level." }, + { "GPSAltitude", "GPSAltitude", "Rational", unsignedRational, xmpInternal, "GPS tag 6, 0x06. Indicates altitude in meters." }, + { "GPSTimeStamp", "GPSTimeStamp", "Date", xmpText, xmpInternal, "GPS tag 29 (date), 0x1D, and, and GPS tag 7 (time), 0x07. Time stamp of GPS data, in Coordinated Universal Time. " + "Note: The GPSDateStamp tag is new in EXIF 2.2. The GPS timestamp in EXIF 2.1 does not include a date. If not present, " + "the date component for the XMP should be taken from exif:DateTimeOriginal, or if that is " + "also lacking from exif:DateTimeDigitized. If no date is available, do not write exif:GPSTimeStamp to XMP." }, + { "GPSSatellites", "GPSSatellites", "Text", xmpText, xmpInternal, "GPS tag 8, 0x08. Satellite information, format is unspecified." }, + { "GPSStatus", "GPSStatus", "Closed Choice of Text", xmpText, xmpInternal, "GPS tag 9, 0x09. Status of GPS receiver at image creation time." }, + { "GPSMeasureMode", "GPSMeasureMode", "Text", xmpText, xmpInternal, "GPS tag 10, 0x0A. GPS measurement mode, Text type." }, + { "GPSDOP", "GPSDOP", "Rational", unsignedRational, xmpInternal, "GPS tag 11, 0x0B. Degree of precision for GPS data." }, + { "GPSSpeedRef", "GPSSpeedRef", "Closed Choice of Text", xmpText, xmpInternal, "GPS tag 12, 0x0C. Units used to speed measurement." }, + { "GPSSpeed", "GPSSpeed", "Rational", unsignedRational, xmpInternal, "GPS tag 13, 0x0D. Speed of GPS receiver movement." }, + { "GPSTrackRef", "GPSTrackRef", "Closed Choice of Text", xmpText, xmpInternal, "GPS tag 14, 0x0E. Reference for movement direction." }, + { "GPSTrack", "GPSTrack", "Rational", unsignedRational, xmpInternal, "GPS tag 15, 0x0F. Direction of GPS movement, values range from 0 to 359.99." }, + { "GPSImgDirectionRef", "GPSImgDirectionRef", "Closed Choice of Text", xmpText, xmpInternal, "GPS tag 16, 0x10. Reference for movement direction." }, + { "GPSImgDirection", "GPSImgDirection", "Rational", unsignedRational, xmpInternal, "GPS tag 17, 0x11. Direction of image when captured, values range from 0 to 359.99." }, + { "GPSMapDatum", "GPSMapDatum", "Text", xmpText, xmpInternal, "GPS tag 18, 0x12. Geodetic survey data." }, + { "GPSDestLatitude", "GPSDestLatitude", "GPSCoordinate", xmpText, xmpInternal, "GPS tag 20, 0x14 (position) and 19, 0x13 (North/South). Indicates destination latitude." }, + { "GPSDestLongitude", "GPSDestLongitude", "GPSCoordinate", xmpText, xmpInternal, "GPS tag 22, 0x16 (position) and 21, 0x15 (East/West). Indicates destination longitude." }, + { "GPSDestBearingRef", "GPSDestBearingRef", "Closed Choice of Text", xmpText, xmpInternal, "GPS tag 23, 0x17. Reference for movement direction." }, + { "GPSDestBearing", "GPSDestBearing", "Rational", unsignedRational, xmpInternal, "GPS tag 24, 0x18. Destination bearing, values from 0 to 359.99." }, + { "GPSDestDistanceRef", "GPSDestDistanceRef", "Closed Choice of Text", xmpText, xmpInternal, "GPS tag 25, 0x19. Units used for speed measurement." }, + { "GPSDestDistance", "GPSDestDistance", "Rational", unsignedRational, xmpInternal, "GPS tag 26, 0x1A. Distance to destination." }, + { "GPSProcessingMethod", "GPSProcessingMethod", "Text", xmpText, xmpInternal, "GPS tag 27, 0x1B. A character string recording the name of the method used for location finding." }, + { "GPSAreaInformation", "GPSAreaInformation", "Text", xmpText, xmpInternal, "GPS tag 28, 0x1C. A character string recording the name of the GPS area." }, + { "GPSDifferential", "GPSDifferential", "Closed Choice of Integer", unsignedShort, xmpInternal, "GPS tag 30, 0x1E. Indicates whether differential correction is applied to the GPS receiver." }, + // End of list marker + { 0, 0, 0, invalidTypeId, xmpInternal, 0 } + }; + + XmpNsInfo::Ns::Ns(const std::string& ns) + : ns_(ns) + { + } + + XmpNsInfo::Prefix::Prefix(const std::string& prefix) + : prefix_(prefix) + { + } + + bool XmpNsInfo::operator==(const XmpNsInfo::Ns& ns) const + { + std::string n(ns_); + return n == ns.ns_; + } + + bool XmpNsInfo::operator==(const XmpNsInfo::Prefix& prefix) const + { + std::string p(prefix_); + return p == prefix.prefix_; + } + + bool XmpPropertyInfo::operator==(const std::string& name) const + { + std::string n(name_); + return n == name; + } + + const char* XmpProperties::propertyTitle(const XmpKey& key) + { + return propertyInfo(key)->title_; + } + + const char* XmpProperties::propertyDesc(const XmpKey& key) + { + return propertyInfo(key)->desc_; + } + + TypeId XmpProperties::propertyType(const XmpKey& key) + { + return propertyInfo(key)->typeId_; + } + + const XmpPropertyInfo* XmpProperties::propertyInfo(const XmpKey& key) + { + const XmpPropertyInfo* pl = propertyList(key.groupName()); + if (!pl) throw Error(36, key.groupName()); + const XmpPropertyInfo* pi = 0; + for (int i = 0; pl[i].name_ != 0; ++i) { + if (std::string(pl[i].name_) == key.tagName()) { + pi = pl + i; + break; + } + } + if (!pi) throw Error(38, key.groupName(), key.tagName()); + return pi; + } + + const char* XmpProperties::ns(const std::string& prefix) + { + return nsInfo(prefix)->ns_; + } + + const char* XmpProperties::nsDesc(const std::string& prefix) + { + return nsInfo(prefix)->desc_; + } + + const XmpPropertyInfo* XmpProperties::propertyList(const std::string& prefix) + { + return nsInfo(prefix)->xmpPropertyInfo_; + } + + const XmpNsInfo* XmpProperties::nsInfo(const std::string& prefix) + { + const XmpNsInfo* xn = find(xmpNsInfo, XmpNsInfo::Prefix(prefix)); + if (!xn) throw Error(35, prefix); + return xn; + } + + const char* XmpProperties::prefix(const std::string& ns) + { + const XmpNsInfo* xn = find(xmpNsInfo, XmpNsInfo::Ns(ns)); + return xn ? xn->prefix_ : 0; + } + + void XmpProperties::printProperties(std::ostream& os, const std::string& prefix) + { + const XmpPropertyInfo* pl = propertyList(prefix); + if (pl) { + const int ck = sizeof(pl) / sizeof(pl[0]); + for (int k = 0; k < ck; ++k) { + os << pl[k]; + } + } + + } // XmpProperties::printProperties + + //! @cond IGNORE + + //! Internal Pimpl structure with private members and data of class XmpKey. + struct XmpKey::Impl { + Impl(); //!< Default constructor + Impl(const std::string& prefix, const std::string& property); //!< Constructor + + /*! + @brief Parse and convert the \em key string into property and prefix. + Updates data members if the string can be decomposed, or throws + \em Error. + + @throw Error if the key cannot be decomposed. + */ + void decomposeKey(const XmpKey* self, const std::string& key); + + // DATA + static const char* familyName_; //!< "Xmp" + + std::string prefix_; //!< Prefix + std::string property_; //!< Property name + }; + //! @endcond + + XmpKey::Impl::Impl() + { + } + + XmpKey::Impl::Impl(const std::string& prefix, const std::string& property) + : prefix_(prefix), property_(property) + { + } + + const char* XmpKey::Impl::familyName_ = "Xmp"; + + XmpKey::XmpKey(const std::string& key) + : p_(new Impl) + { + p_->decomposeKey(this, key); + } + + XmpKey::XmpKey(const std::string& prefix, const std::string& property) + : p_(new Impl(prefix, property)) + { + // Validate prefix and property, throws + XmpProperties::propertyInfo(*this); + } + + XmpKey::~XmpKey() + { + delete p_; + } + + XmpKey::XmpKey(const XmpKey& rhs) + : Key(rhs), p_(new Impl(*rhs.p_)) + { + } + + XmpKey& XmpKey::operator=(const XmpKey& rhs) + { + if (this == &rhs) return *this; + *p_ = *rhs.p_; + return *this; + } + + XmpKey::AutoPtr XmpKey::clone() const + { + return AutoPtr(clone_()); + } + + XmpKey* XmpKey::clone_() const + { + return new XmpKey(*this); + } + + std::string XmpKey::key() const + { + return std::string(p_->familyName_) + "." + p_->prefix_ + "." + p_->property_; + } + + const char* XmpKey::familyName() const + { + return p_->familyName_; + } + + std::string XmpKey::groupName() const + { + return p_->prefix_; + } + + std::string XmpKey::tagName() const + { + return p_->property_; + } + + std::string XmpKey::tagLabel() const + { + return XmpProperties::propertyTitle(*this); + } + + const char* XmpKey::ns() const + { + return XmpProperties::ns(p_->prefix_); + } + + void XmpKey::Impl::decomposeKey(const XmpKey* self, const std::string& key) + { + // Get the family name, prefix and property name parts of the key + std::string::size_type pos1 = key.find('.'); + if (pos1 == std::string::npos) throw Error(6, key); + std::string familyName = key.substr(0, pos1); + if (familyName != std::string(familyName_)) { + throw Error(6, key); + } + std::string::size_type pos0 = pos1 + 1; + pos1 = key.find('.', pos0); + if (pos1 == std::string::npos) throw Error(6, key); + std::string prefix = key.substr(pos0, pos1 - pos0); + if (prefix == "") throw Error(6, key); + std::string property = key.substr(pos1 + 1); + if (property == "") throw Error(6, key); + + property_ = property; + prefix_ = prefix; + + // Validate prefix and property + XmpProperties::propertyInfo(*self); + + } // XmpKey::Impl::decomposeKey + + // ************************************************************************* + // free functions + std::ostream& operator<<(std::ostream& os, const XmpPropertyInfo& property) + { + return os << property.name_ << ",\t" + << property.title_ << ",\t" + << property.xmpValueType_ << ",\t" + << TypeInfo::typeName(property.typeId_) << ",\t" + << ( property.xmpCategory_ == xmpExternal ? "External" : "Internal" ) << ",\t" + << property.desc_ << "\n"; + } + +} // namespace Exiv2 diff --git a/src/properties.hpp b/src/properties.hpp new file mode 100644 index 00000000..19dad8d1 --- /dev/null +++ b/src/properties.hpp @@ -0,0 +1,262 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2007 Andreas Huggel + * + * This program is part of the Exiv2 distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. + */ +/*! + @file properties.hpp + @brief XMP property and type information.
References:
+ XMP Specification from Adobe + (Property descriptions copied from the XMP specification with the permission of Adobe) + @version $Rev$ + @author Andreas Huggel (ahu) + ahuggel@gmx.net + @date 13-Jul-07, ahu: created + */ +#ifndef PROPERTIES_HPP_ +#define PROPERTIES_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "metadatum.hpp" + +// + standard includes +#include +#include +#include + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class XmpKey; + +// ***************************************************************************** +// class definitions + + //! Category of an XMP property + enum XmpCategory { xmpInternal, xmpExternal }; + + //! Information about one XMP property. + struct XmpPropertyInfo { + //! Comparison operator for name + bool operator==(const std::string& name) const; + + const char* name_; //!< Property name + const char* title_; //!< Property title or label + const char* xmpValueType_; //!< XMP value type (for info only) + TypeId typeId_; //!< Exiv2 default type for the property + XmpCategory xmpCategory_; //!< Category (internal or external) + const char* desc_; //!< Property description + }; + + //! Structure mapping XMP namespaces and (preferred) prefixes. + struct XmpNsInfo { + //! For comparison with prefix + struct Prefix { + //! Constructor. + Prefix(const std::string& prefix); + //! The prefix string. + std::string prefix_; + }; + //! For comparison with namespace + struct Ns { + //! Constructor. + Ns(const std::string& ns); + //! The namespace string + std::string ns_; + }; + //! Comparison operator for namespace + bool operator==(const Ns& ns) const; + //! Comparison operator for prefix + bool operator==(const Prefix& prefix) const; + + const char* ns_; //!< Namespace + const char* prefix_; //!< (Preferred) prefix + const XmpPropertyInfo* xmpPropertyInfo_; //!< List of known properties + const char* desc_; //!< Brief description of the namespace + }; + + //! Container for XMP property information. Implemented as a static class. + class XmpProperties { + //! Prevent construction: not implemented. + XmpProperties(); + //! Prevent copy-construction: not implemented. + XmpProperties(const XmpProperties& rhs); + //! Prevent assignment: not implemented. + XmpProperties& operator=(const XmpProperties& rhs); + + public: + /*! + @brief Return the title (label) of the property. + @param key The property key + @return The title (label) of the property + @throw Error if the key is invalid. + */ + static const char* propertyTitle(const XmpKey& key); + /*! + @brief Return the description of the property. + @param key The property key + @return The description of the property + @throw Error if the key is invalid. + */ + static const char* propertyDesc(const XmpKey& key); + /*! + @brief Return the type for property \em key + @param key The property key + @return The type of the property + @throw Error if the key is invalid. + */ + static TypeId propertyType(const XmpKey& key); + /*! + @brief Return information for the property for key. + Always returns a valid pointer. + @param key The property key + @return a pointer to the property information + @throw Error if the key is invalid. + */ + static const XmpPropertyInfo* propertyInfo(const XmpKey& key); + /*! + @brief Return the namespace name for the schema associated + with \em prefix. + @param prefix Prefix + @return the namespace name + @throw Error if no namespace is registered with \em prefix. + */ + static const char* ns(const std::string& prefix); + /*! + @brief Return the namespace description for the schema associated + with \em prefix. + @param prefix Prefix + @return the namespace description + @throw Error if no namespace is registered with \em prefix. + */ + static const char* nsDesc(const std::string& prefix); + /*! + @brief Return read-only list of built-in properties for \em prefix. + @param prefix Prefix + @return Pointer to the built-in properties for prefix, may be 0 if + none is configured in the namespace info. + @throw Error if no namespace is registered with \em prefix. + */ + static const XmpPropertyInfo* propertyList(const std::string& prefix); + /*! + @brief Return information about a schema namespace for \em prefix. + Always returns a valid pointer. + @param prefix The prefix + @return a pointer to the related information + @throw Error if no namespace is registered with \em prefix. + */ + static const XmpNsInfo* nsInfo(const std::string& prefix); + /*! + @brief Return the (preferred) prefix for schema namespace \em ns + @param ns Schema namespace + @return the prefix or 0 if namespace \em ns is not registered. + */ + static const char* prefix(const std::string& ns); + //! Print a list of properties of a schema namespace to output stream \em os. + static void printProperties(std::ostream& os, const std::string& prefix); + + }; // class XmpProperties + + /*! + @brief Concrete keys for XMP metadata. + */ + class XmpKey : public Key { + public: + //! Shortcut for an %XmpKey auto pointer. + typedef std::auto_ptr AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor to create an XMP key from a key string. + + @param key The key string. + @throw Error if the first part of the key is not 'Xmp' or + the remaining parts of the key cannot be parsed and + converted to a known schema prefix and property name. + */ + explicit XmpKey(const std::string& key); + /*! + @brief Constructor to create an XMP key from a schema prefix + and a property name. + + @param prefix Schema prefix name + @param property Property name + + @throw Error if the schema prefix or the property name are not + known. + */ + XmpKey(const std::string& prefix, const std::string& property); + //! Copy constructor. + XmpKey(const XmpKey& rhs); + //! Virtual destructor. + virtual ~XmpKey(); + //@} + + //! @name Manipulators + //@{ + //! Assignment operator. + XmpKey& operator=(const XmpKey& rhs); + //@} + + //! @name Accessors + //@{ + virtual std::string key() const; + virtual const char* familyName() const; + /*! + @brief Return the name of the group (the second part of the key). + For XMP keys, the group name is the schema prefix name. + */ + virtual std::string groupName() const; + virtual std::string tagName() const; + virtual std::string tagLabel() const; + //! Properties don't have a tag number. Return 0. + virtual uint16_t tag() const { return 0; } + + AutoPtr clone() const; + + // Todo: Should this be removed? What about tagLabel then? + //! Return the schema namespace for the prefix of the key + const char* ns() const; + //@} + + private: + //! Internal virtual copy constructor. + virtual XmpKey* clone_() const; + + private: + // Pimpl idiom + struct Impl; + Impl* p_; + + }; // class XmpKey + +// ***************************************************************************** +// free functions + + //! Output operator for property info + std::ostream& operator<<(std::ostream& os, const XmpPropertyInfo& propertyInfo); + +} // namespace Exiv2 + +#endif // #ifndef PROPERTIES_HPP_ diff --git a/src/tags.cpp b/src/tags.cpp index 439f10ad..2b87ba9e 100644 --- a/src/tags.cpp +++ b/src/tags.cpp @@ -1620,7 +1620,8 @@ namespace Exiv2 { int32_t denominator; char c; is >> nominator >> c >> denominator; - if (is && c == '/') r = std::make_pair(nominator, denominator); + if (c != '/') is.setstate(std::ios::failbit); + if (is) r = std::make_pair(nominator, denominator); return is; } @@ -1633,9 +1634,10 @@ namespace Exiv2 { { uint32_t nominator; uint32_t denominator; - char c; + char c('\0'); is >> nominator >> c >> denominator; - if (is && c == '/') r = std::make_pair(nominator, denominator); + if (c != '/') is.setstate(std::ios::failbit); + if (is) r = std::make_pair(nominator, denominator); return is; } diff --git a/src/tiffvisitor.cpp b/src/tiffvisitor.cpp index 32a6ec59..a7baf3ce 100644 --- a/src/tiffvisitor.cpp +++ b/src/tiffvisitor.cpp @@ -216,8 +216,21 @@ namespace Exiv2 { long size = 0; getObjData(pData, size, 0x02bc, Group::ifd0, object); if (pData) { - pImage_->xmpPacket().assign( - std::string(reinterpret_cast(pData), size)); + std::string& xmpPacket = pImage_->xmpPacket(); + xmpPacket.assign(reinterpret_cast(pData), size); + std::string::size_type idx = xmpPacket.find_first_of('<'); + if (idx != std::string::npos && idx > 0) { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: Removing " << idx << " characters " + << "from the beginning of the XMP packet\n"; +#endif + xmpPacket = xmpPacket.substr(idx); + } + if (XmpParser::decode(pImage_->xmpData(), xmpPacket)) { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: Failed to decode XMP metadata.\n"; +#endif + } } } // TiffMetadataDecoder::decodeXmp diff --git a/src/types.cpp b/src/types.cpp index fb32d19c..3e593c16 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -72,6 +72,7 @@ namespace Exiv2 { TypeInfoTable(time, "Time", 11), TypeInfoTable(comment, "Comment", 1), TypeInfoTable(directory, "Directory", 1), + TypeInfoTable(xmpText, "XmpText", 1), // End of list marker TypeInfoTable(lastTypeId, "(Unknown)", 0) }; diff --git a/src/types.hpp b/src/types.hpp index 02b67626..557b357a 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -90,7 +90,7 @@ namespace Exiv2 { enum ByteOrder { invalidByteOrder, littleEndian, bigEndian }; //! An identifier for each type of metadata - enum MetadataId { mdExif=1, mdIptc=2, mdComment=4 }; + enum MetadataId { mdExif=1, mdIptc=2, mdComment=4, mdXmp=8 }; //! An identifier for each mode of metadata support enum AccessMode { amNone=0, amRead=1, amWrite=2, amReadWrite=3 }; @@ -102,6 +102,7 @@ namespace Exiv2 { string, date, time, comment, directory, + xmpText, lastTypeId }; // Todo: decentralize IfdId, so that new ids can be defined elsewhere @@ -388,6 +389,16 @@ namespace Exiv2 { return os.str(); } + //! Utility function to convert a string to a value of type T. + template + T stringTo(const std::string& s, bool& ok) + { + std::istringstream is(s); + T tmp; + ok = is >> tmp ? true : false; + return tmp; + } + /*! @brief Return the greatest common denominator of n and m. (implementation from Boost rational.hpp) diff --git a/src/value.cpp b/src/value.cpp index 18dfa27a..82117aaa 100644 --- a/src/value.cpp +++ b/src/value.cpp @@ -44,6 +44,7 @@ EXIV2_RCSID("@(#) $Id$") #include #include #include +#include // ***************************************************************************** // class member definitions @@ -105,6 +106,9 @@ namespace Exiv2 { case comment: value = AutoPtr(new CommentValue); break; + case xmpText: + value = AutoPtr(new XmpTextValue); + break; default: value = AutoPtr(new DataValue(typeId)); break; @@ -124,6 +128,11 @@ namespace Exiv2 { return os.str(); } + std::string Value::toString(long /*n*/) const + { + return toString(); + } + DataValue& DataValue::operator=(const DataValue& rhs) { if (this == &rhs) return *this; @@ -144,7 +153,9 @@ namespace Exiv2 { std::istringstream is(buf); int tmp; value_.clear(); - while (is >> tmp) { + while (!(is.eof())) { + is >> tmp; + if (is.fail()) return 1; value_.push_back(static_cast(tmp)); } return 0; @@ -177,6 +188,13 @@ namespace Exiv2 { return os; } + std::string DataValue::toString(long n) const + { + std::ostringstream os; + os << static_cast(value_[n]); + return os.str(); + } + StringValueBase& StringValueBase::operator=(const StringValueBase& rhs) { if (this == &rhs) return *this; @@ -369,6 +387,115 @@ namespace Exiv2 { return new CommentValue(*this); } + XmpTextValue& XmpTextValue::operator=(const XmpTextValue& rhs) + { + if (this == &rhs) return *this; + Value::operator=(rhs); + value_ = rhs.value_; + return *this; + } + + int XmpTextValue::read(const std::string& buf) + { + std::string::size_type start = 0; + bool escaped = false; + + for (std::string::size_type i = 0; i < buf.size(); ++i) { + if (start == 0) { + // skip whitespace leading to the quote + if (isspace(buf[i])) continue; + // first character after that must be a quote + if (buf[i] == quoteChar[0]) { + start = i + 1; + continue; + } + return 1; + } + // look for the first unescaped quote + if (buf[i] == escapeChar[0] && !escaped) { + escaped = true; + continue; + } + if (buf[i] == quoteChar[0] && !escaped) { + value_.push_back(buf.substr(start, i - start)); + // remove escape characters + unescapeText(value_.back()); + start = 0; + continue; + } + escaped = false; + } + // check for premature end of string + if (escaped || start != 0) return 2; + + return 0; + } + + int XmpTextValue::read(const byte* buf, + long len, + ByteOrder /*byteOrder*/) + { + std::string s(reinterpret_cast(buf), len); + return read(s); + } + + long XmpTextValue::copy(byte* buf, + ByteOrder /*byteOrder*/) const + { + std::ostringstream os; + write(os); + std::string s = os.str(); + memcpy(buf, &s[0], s.size()); + return s.size(); + } + + long XmpTextValue::size() const + { + std::ostringstream os; + write(os); + return os.str().size(); + } + + std::ostream& XmpTextValue::write(std::ostream& os) const + { + for (std::vector::const_iterator i = value_.begin(); + i != value_.end(); ++i) { + if (i != value_.begin()) os << " "; + std::string s(*i); + quoteText(s); + os << s; + } + return os; + } + + std::string XmpTextValue::toString(long n) const + { + return value_[n]; + } + + long XmpTextValue::toLong(long n) const + { + bool ok; + return stringTo(value_[n], ok); + } + + float XmpTextValue::toFloat(long n) const + { + bool ok; + return stringTo(value_[n], ok); + } + + Rational XmpTextValue::toRational(long n) const + { + bool ok; + return stringTo(value_[n], ok); + } + + XmpTextValue* XmpTextValue::clone_() const + { + return new XmpTextValue(*this); + } + DateValue::DateValue(int year, int month, int day) : Value(date) { @@ -636,4 +763,31 @@ namespace Exiv2 { return result; } +// ***************************************************************************** +// free functions + + const char quoteChar[] = "\""; + const char escapeChar[] = "\\"; + + void quoteText(std::string& text) + { + for (std::string::iterator i = text.begin(); i != text.end(); ++i) { + if (*i == escapeChar[0] || *i == quoteChar[0]) { + i = text.insert(i, escapeChar[0]); + if (++i == text.end()) break; + } + } + text = quoteChar + text + quoteChar; + } // quoteText + + void unescapeText(std::string& text) + { + for (std::string::iterator i = text.begin(); i != text.end(); ++i) { + if (*i == escapeChar[0]) { + i = text.erase(i); // returns next pos, i.e., skips the escaped char + if (i == text.end()) break; + } + } + } // unescapeText + } // namespace Exiv2 diff --git a/src/value.hpp b/src/value.hpp index 115f81a4..54be0c73 100644 --- a/src/value.hpp +++ b/src/value.hpp @@ -117,11 +117,6 @@ namespace Exiv2 { //@{ //! Return the type identifier (Exif data format type). TypeId typeId() const { return type_; } - /*! - @brief Return the value as a string. Implemented in terms of - write(std::ostream& os) const of the concrete class. - */ - std::string toString() const; /*! @brief Return an auto-pointer to a copy of itself (deep copy). The caller owns this copy and the auto-pointer ensures that @@ -151,25 +146,37 @@ namespace Exiv2 { */ virtual std::ostream& write(std::ostream& os) const =0; /*! - @brief Convert the n-th component of the value to a long. The - behaviour of this method may be undefined if there is no - n-th component. + @brief Return the value as a string. Implemented in terms of + write(std::ostream& os) const of the concrete class. + */ + std::string toString() const; + /*! + @brief Return the n-th component of the value as a string. + The default implementation returns toString(). The behaviour + of this method may be undefined if there is no n-th + component. + */ + virtual std::string toString(long n) const; + /*! + @brief Convert the n-th component of the value to a long. + The behaviour of this method may be undefined if there is no + n-th component. @return The converted value. */ virtual long toLong(long n =0) const =0; /*! - @brief Convert the n-th component of the value to a float. The - behaviour of this method may be undefined if there is no - n-th component. + @brief Convert the n-th component of the value to a float. + The behaviour of this method may be undefined if there is no + n-th component. @return The converted value. */ virtual float toFloat(long n =0) const =0; /*! - @brief Convert the n-th component of the value to a Rational. The - behaviour of this method may be undefined if there is no - n-th component. + @brief Convert the n-th component of the value to a Rational. + The behaviour of this method may be undefined if there is no + n-th component. @return The converted value. */ @@ -212,6 +219,7 @@ namespace Exiv2 { date%DateValue time%TimeValue comment%CommentValue + xmpText%XmpTextValue default:%DataValue(typeId) @@ -304,6 +312,12 @@ namespace Exiv2 { virtual long count() const { return size(); } virtual long size() const; virtual std::ostream& write(std::ostream& os) const; + /*! + @brief Return the n-th component of the value as a string. + The behaviour of this method may be undefined if there is no + n-th component. + */ + virtual std::string toString(long n) const; virtual long toLong(long n =0) const { return value_[n]; } virtual float toFloat(long n =0) const { return value_[n]; } virtual Rational toRational(long n =0) const @@ -602,6 +616,103 @@ namespace Exiv2 { }; // class CommentValue + /*! + @brief %Value type suitable for XMP Text properties and arrays thereof. + + Uses a vector of std::string to store the text(s). + */ + class XmpTextValue : public Value { + public: + //! Shortcut for a %XmpTextValue auto pointer. + typedef std::auto_ptr AutoPtr; + + //! @name Creators + //@{ + //! Constructor for subclasses + XmpTextValue() + : Value(xmpText) {} + //! Constructor for subclasses + XmpTextValue(const std::string& buf) + : Value(xmpText) { read(buf); } + //! Copy constructor + XmpTextValue(const XmpTextValue& rhs) + : Value(rhs), value_(rhs.value_) {} + + //! Virtual destructor. + virtual ~XmpTextValue() {} + //@} + + //! @name Manipulators + //@{ + //! Assignment operator. + XmpTextValue& operator=(const XmpTextValue& rhs); + /*! + @brief Read a text property value or array items from \em buf. + + Expects a list of quoted strings, one for each array item. The + quoted strings may be separated by whitespace. Double quotes in + the strings must be escaped with a backslash character: \\". + Examples: ""\\"foo\\", he said"" or ""foo" "bar"". + */ + virtual int read(const std::string& buf); + /*! + @brief Read the value from a character buffer. + + Uses read(const std::string& buf) + + @note The byte order is required by the interface but not used by this + method, so just use the default. + + @param buf Pointer to the data buffer to read from + @param len Number of bytes in the data buffer + @param byteOrder Byte order. Not needed. + + @return 0 if successful. + */ + virtual int read(const byte* buf, + long len, + ByteOrder byteOrder =invalidByteOrder); + //@} + + //! @name Accessors + //@{ + AutoPtr clone() const { return AutoPtr(clone_()); } + /*! + @brief Write value to a character data buffer. + + The user must ensure that the buffer has enough memory. Otherwise + the call results in undefined behaviour. + + @note The byte order is required by the interface but not used by this + method, so just use the default. + + @param buf Data buffer to write to. + @param byteOrder Byte order. Not used. + @return Number of characters written. + */ + virtual long copy(byte* buf, ByteOrder byteOrder =invalidByteOrder) const; + virtual long count() const { return static_cast(value_.size()); } + virtual long size() const; + /*! + @brief Return the n-th component of the value as a string. + The behaviour of this method may be undefined if there is no + n-th component. + */ + virtual std::string toString(long n) const; + virtual long toLong(long n =0) const; + virtual float toFloat(long n =0) const; + virtual Rational toRational(long n =0) const; + virtual std::ostream& write(std::ostream& os) const; + //@} + + protected: + //! Internal virtual copy constructor. + virtual XmpTextValue* clone_() const; + // DATA + std::vector value_; //!< Stores the string values. + + }; // class XmpTextValue + /*! @brief %Value for simple ISO 8601 dates @@ -685,13 +796,13 @@ namespace Exiv2 { virtual const Date& getDate() const { return date_; } virtual long count() const { return size(); } virtual long size() const; - /*! - @brief Write the value to an output stream. . - */ virtual std::ostream& write(std::ostream& os) const; + //! Return the value as a UNIX calender time converted to long. virtual long toLong(long n =0) const; + //! Return the value as a UNIX calender time converted to float. virtual float toFloat(long n =0) const { return static_cast(toLong(n)); } + //! Return the value as a UNIX calender time converted to Rational. virtual Rational toRational(long n =0) const { return Rational(toLong(n), 1); } //@} @@ -795,11 +906,13 @@ namespace Exiv2 { virtual const Time& getTime() const { return time_; } virtual long count() const { return size(); } virtual long size() const; - //! Write the value to an output stream. . virtual std::ostream& write(std::ostream& os) const; + //! Returns number of seconds in the day in UTC. virtual long toLong(long n =0) const; + //! Returns number of seconds in the day in UTC converted to float. virtual float toFloat(long n =0) const { return static_cast(toLong(n)); } + //! Returns number of seconds in the day in UTC converted to Rational. virtual Rational toRational(long n =0) const { return Rational(toLong(n), 1); } //@} @@ -841,6 +954,7 @@ namespace Exiv2 { Time time_; }; // class TimeValue + //! Template to determine the TypeId for a type T template TypeId getType(); @@ -858,7 +972,7 @@ namespace Exiv2 { template<> inline TypeId getType() { return signedRational; } // No default implementation: let the compiler/linker complain -// template inline TypeId getType() { return invalid; } + // template inline TypeId getType() { return invalid; } /*! @brief Template for a %Value of a basic type. This is used for unsigned @@ -910,6 +1024,13 @@ namespace Exiv2 { virtual long count() const { return static_cast(value_.size()); } virtual long size() const; virtual std::ostream& write(std::ostream& os) const; + /*! + @brief Return the n-th component of the value as a string. + The behaviour of this method may be undefined if there is no + n-th + component. + */ + virtual std::string toString(long n) const; virtual long toLong(long n =0) const; virtual float toFloat(long n =0) const; virtual Rational toRational(long n =0) const; @@ -963,8 +1084,21 @@ namespace Exiv2 { typedef ValueType RationalValue; // ***************************************************************************** -// template and inline definitions +// free functions, template and inline definitions + //! Double-quote character + extern const char quoteChar[]; + //! Escape character + extern const char escapeChar[]; + /*! + @brief Quote a string with double-quotes, escape quotes and escape + characters in the string. + */ + void quoteText(std::string& text); + /*! + @brief Remove escape characters from an unquoted string. + */ + void unescapeText(std::string& text); /*! @brief Read a value of type T from the data buffer. @@ -1150,7 +1284,9 @@ namespace Exiv2 { std::istringstream is(buf); T tmp; value_.clear(); - while (is >> tmp) { + while (!(is.eof())) { + is >> tmp; + if (is.fail()) return 1; value_.push_back(tmp); } return 0; @@ -1190,6 +1326,13 @@ namespace Exiv2 { } return os; } + + template + inline std::string ValueType::toString(long n) const + { + return Exiv2::toString(value_[n]); + } + // Default implementation template inline long ValueType::toLong(long n) const @@ -1264,7 +1407,6 @@ namespace Exiv2 { sizeDataArea_ = len; return 0; } - } // namespace Exiv2 #endif // #ifndef VALUE_HPP_ diff --git a/src/version.hpp b/src/version.hpp index 94786653..1452f43c 100644 --- a/src/version.hpp +++ b/src/version.hpp @@ -39,7 +39,7 @@ /*! @brief %Exiv2 MINOR version number of the library used at compile-time. */ -#define EXIV2_MINOR_VERSION (15) +#define EXIV2_MINOR_VERSION (16) /*! @brief %Exiv2 PATCH version number of the library used at compile-time. */ diff --git a/src/xmp.cpp b/src/xmp.cpp new file mode 100644 index 00000000..d3f406d7 --- /dev/null +++ b/src/xmp.cpp @@ -0,0 +1,558 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2007 Andreas Huggel + * + * This program is part of the Exiv2 distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. + */ +/* + File: xmp.cpp + Version: $Rev$ + Author(s): Andreas Huggel (ahu) + History: 13-July-07, ahu: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id$") + +// ***************************************************************************** +// included header files +#include "xmp.hpp" +#include "types.hpp" +#include "error.hpp" +#include "value.hpp" +#include "properties.hpp" + +// + standard includes +#include +#include +#include +#include + +// Adobe XMP Toolkit +#ifdef EXV_HAVE_XMP_TOOLKIT +# define TXMP_STRING_TYPE std::string +# include +# include +#endif // EXV_HAVE_XMP_TOOLKIT + +// ***************************************************************************** +namespace { + //! Unary predicate that matches an Xmpdatum by key + class FindXmpdatum { + public: + //! Constructor, initializes the object with key + FindXmpdatum(const Exiv2::XmpKey& key) + : key_(key.key()) {} + /*! + @brief Returns true if prefix and property of the argument + Xmpdatum are equal to that of the object. + */ + bool operator()(const Exiv2::Xmpdatum& xmpdatum) const + { return key_ == xmpdatum.key(); } + + private: + std::string key_; + + }; // class FindXmpdatum + + //! Make an XMP key from a schema namespace and property path + Exiv2::XmpKey::AutoPtr makeXmpKey(const std::string& schemaNs, + const std::string& propPath); +} + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + //! @cond IGNORE + + //! Internal Pimpl structure with private members and data of class Xmpdatum. + struct Xmpdatum::Impl { + Impl(const XmpKey& key, const Value* pValue); //!< Constructor + Impl(const Impl& rhs); //!< Copy constructor + Impl& operator=(const Impl& rhs); //!< Assignment + + // DATA + XmpKey::AutoPtr key_; //!< Key + Value::AutoPtr value_; //!< Value + }; + //! @endcond + + Xmpdatum::Impl::Impl(const XmpKey& key, const Value* pValue) + : key_(key.clone()) + { + if (pValue) value_ = pValue->clone(); + } + + Xmpdatum::Impl::Impl(const Impl& rhs) + { + if (rhs.key_.get() != 0) key_ = rhs.key_->clone(); // deep copy + if (rhs.value_.get() != 0) value_ = rhs.value_->clone(); // deep copy + } + + Xmpdatum::Impl::Impl& Xmpdatum::Impl::operator=(const Impl& rhs) + { + if (this == &rhs) return *this; + key_.reset(); + if (rhs.key_.get() != 0) key_ = rhs.key_->clone(); // deep copy + value_.reset(); + if (rhs.value_.get() != 0) value_ = rhs.value_->clone(); // deep copy + return *this; + } + + Xmpdatum::Xmpdatum(const XmpKey& key, const Value* pValue) + : p_(new Impl(key, pValue)) + { + } + + Xmpdatum::Xmpdatum(const Xmpdatum& rhs) + : Metadatum(rhs), p_(new Impl(*rhs.p_)) + { + } + + Xmpdatum& Xmpdatum::operator=(const Xmpdatum& rhs) + { + if (this == &rhs) return *this; + Metadatum::operator=(rhs); + *p_ = *rhs.p_; + return *this; + } + + Xmpdatum::~Xmpdatum() + { + delete p_; + } + + std::string Xmpdatum::key() const + { + return p_->key_.get() == 0 ? "" : p_->key_->key(); + } + + std::string Xmpdatum::groupName() const + { + return p_->key_.get() == 0 ? "" : p_->key_->groupName(); + } + + std::string Xmpdatum::tagName() const + { + return p_->key_.get() == 0 ? "" : p_->key_->tagName(); + } + + std::string Xmpdatum::tagLabel() const + { + return p_->key_.get() == 0 ? "" : p_->key_->tagLabel(); + } + + uint16_t Xmpdatum::tag() const + { + return p_->key_.get() == 0 ? 0 : p_->key_->tag(); + } + + TypeId Xmpdatum::typeId() const + { + return p_->value_.get() == 0 ? invalidTypeId : p_->value_->typeId(); + } + + const char* Xmpdatum::typeName() const + { + return TypeInfo::typeName(typeId()); + } + + long Xmpdatum::count() const + { + return p_->value_.get() == 0 ? 0 : p_->value_->count(); + } + + long Xmpdatum::size() const + { + return p_->value_.get() == 0 ? 0 : p_->value_->size(); + } + + std::string Xmpdatum::toString() const + { + return p_->value_.get() == 0 ? "" : p_->value_->toString(); + } + + std::string Xmpdatum::toString(long n) const + { + return p_->value_.get() == 0 ? "" : p_->value_->toString(n); + } + + long Xmpdatum::toLong(long n) const + { + return p_->value_.get() == 0 ? -1 : p_->value_->toLong(n); + } + + float Xmpdatum::toFloat(long n) const + { + return p_->value_.get() == 0 ? -1 : p_->value_->toFloat(n); + } + + Rational Xmpdatum::toRational(long n) const + { + return p_->value_.get() == 0 ? Rational(-1, 1) : p_->value_->toRational(n); + } + + Value::AutoPtr Xmpdatum::getValue() const + { + return p_->value_.get() == 0 ? Value::AutoPtr(0) : p_->value_->clone(); + } + + const Value& Xmpdatum::value() const + { + if (p_->value_.get() == 0) throw Error(8); + return *p_->value_; + } + + long Xmpdatum::copy(byte* /*buf*/, ByteOrder /*byteOrder*/) const + { + throw Error(34, "Xmpdatum::copy"); + return 0; + } + + Xmpdatum& Xmpdatum::operator=(const uint16_t& value) + { + UShortValue::AutoPtr v(new UShortValue); + v->value_.push_back(value); + p_->value_ = v; + return *this; + } + + Xmpdatum& Xmpdatum::operator=(const std::string& value) + { + setValue(value); + return *this; + } + + Xmpdatum& Xmpdatum::operator=(const Value& value) + { + setValue(&value); + return *this; + } + + void Xmpdatum::setValue(const Value* pValue) + { + p_->value_.reset(); + if (pValue) p_->value_ = pValue->clone(); + } + + void Xmpdatum::setValue(const std::string& value) + { + // Todo: What's the correct default? Adjust doc + if (p_->value_.get() == 0) { + assert(0 != p_->key_.get()); + TypeId type = XmpProperties::propertyType(*p_->key_.get()); + p_->value_ = Value::create(type); + } + p_->value_->read(value); + } + + Xmpdatum& XmpData::operator[](const std::string& key) + { + XmpKey xmpKey(key); + iterator pos = findKey(xmpKey); + if (pos == end()) { + add(Xmpdatum(xmpKey)); + pos = findKey(xmpKey); + } + return *pos; + } + + int XmpData::add(const XmpKey& key, Value* value) + { + return add(Xmpdatum(key, value)); + } + + int XmpData::add(const Xmpdatum& xmpDatum) + { + xmpMetadata_.push_back(xmpDatum); + return 0; + } + + XmpData::const_iterator XmpData::findKey(const XmpKey& key) const + { + return std::find_if(xmpMetadata_.begin(), xmpMetadata_.end(), + FindXmpdatum(key)); + } + + XmpData::iterator XmpData::findKey(const XmpKey& key) + { + return std::find_if(xmpMetadata_.begin(), xmpMetadata_.end(), + FindXmpdatum(key)); + } + + void XmpData::clear() + { + xmpMetadata_.clear(); + } + + void XmpData::sortByKey() + { + std::sort(xmpMetadata_.begin(), xmpMetadata_.end(), cmpMetadataByKey); + } + + XmpData::const_iterator XmpData::begin() const + { + return xmpMetadata_.begin(); + } + + XmpData::const_iterator XmpData::end() const + { + return xmpMetadata_.end(); + } + + bool XmpData::empty() const + { + return count() == 0; + } + + long XmpData::count() const + { + return static_cast(xmpMetadata_.size()); + } + + XmpData::iterator XmpData::begin() + { + return xmpMetadata_.begin(); + } + + XmpData::iterator XmpData::end() + { + return xmpMetadata_.end(); + } + + XmpData::iterator XmpData::erase(XmpData::iterator pos) + { + return xmpMetadata_.erase(pos); + } + + bool XmpParser::initialized_ = false; + +#ifdef EXV_HAVE_XMP_TOOLKIT + int XmpParser::decode( XmpData& xmpData, + const std::string& xmpPacket) + { try { + xmpData.clear(); + + if (!initialized_) { + initialized_ = true; + if (!SXMPMeta::Initialize()) { +#ifndef SUPPRESS_WARNINGS + std::cerr << "XMP Toolkit initialization failed.\n"; +#endif + return 2; + } + } + SXMPMeta meta(xmpPacket.data(), xmpPacket.size()); + SXMPIterator iter(meta); + std::string schemaNs, propPath, propValue; + XMP_OptionBits opt; + while (iter.Next(&schemaNs, &propPath, &propValue, &opt)) { + if (XMP_NodeIsSchema(opt)) continue; + + XmpKey::AutoPtr key = makeXmpKey(schemaNs, propPath); + if (key.get() == 0) continue; + + // Create an Exiv2 value and read the property value + Value::AutoPtr val = Value::create(XmpProperties::propertyType(*key.get())); + if (XMP_PropIsSimple(opt)) { + if (val->typeId() != xmpText) { + int ret = val->read(propValue); + if (ret != 0) val = Value::create(xmpText); + } + if (val->typeId() == xmpText) { + std::string pv = propValue; + // Todo: Do not use read() for XmpTextValues + quoteText(pv); + val->read(pv); + } + } + else if (XMP_PropIsArray(opt)) { + XMP_Index itemIdx = 1; + std::string itemValue, arrayValue; + XMP_OptionBits itemOpt; + while (meta.GetArrayItem(schemaNs.c_str(), propPath.c_str(), + itemIdx, &itemValue, &itemOpt)) { + if (val->typeId() == xmpText) quoteText(itemValue); + if (itemIdx > 1) arrayValue += " "; + arrayValue += itemValue; + ++itemIdx; + } + iter.Skip(kXMP_IterSkipSubtree); + // Todo: Do not use read() for XmpTextValues + val->read(arrayValue); + } + else { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: XMP property " << key->key() + << " has unsupported property type; skipping property.\n"; +#endif + iter.Skip(kXMP_IterSkipSubtree); + continue; + } + + xmpData.add(*key.get(), val.get()); + } + + return 0; + } + catch (const XMP_Error& e) { +#ifndef SUPPRESS_WARNINGS + std::cerr << Error(39, e.GetID(), e.GetErrMsg()) << "\n"; +#endif + xmpData.clear(); + return 3; + }} // XmpParser::decode +#else + int XmpParser::decode( XmpData& /*xmpData*/, + const std::string& xmpPacket) + { + if (!xmpPacket.empty()) { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: XMP toolkit support not compiled in.\n"; +#endif + } + return 1; + } // XmpParser::decode +#endif // !EXV_HAVE_XMP_TOOLKIT + +#ifdef EXV_HAVE_XMP_TOOLKIT + int XmpParser::encode( std::string& xmpPacket, + const XmpData& xmpData) + { try { + xmpPacket.clear(); + + if (!initialized_) { + initialized_ = true; + if (!SXMPMeta::Initialize()) { +#ifndef SUPPRESS_WARNINGS + std::cerr << "XMP Toolkit initialization failed.\n"; +#endif + return 2; + } + } + + SXMPMeta meta; + + for (XmpData::const_iterator i = xmpData.begin(); i != xmpData.end(); ++i) { + + const char* ns = XmpProperties::ns(i->groupName()); + + // Todo: Make sure the namespace is registered with XMP-SDK + + // Todo: Requires a separate indicator for array + // (or value type in general => reuse typeId?) + + if (i->count() == 1) { + // simple property +//-ahu +std::cerr << i->key() << "\n"; + meta.SetProperty(ns, i->tagName().c_str(), i->toString(0).c_str()); + + } + if (i->count() > 1) { + // array or sequence + for (int k = 0; k < i->count(); ++k) { + + // Todo: Need indicator if array is ordered + +//-ahu +std::cerr << i->key() << " count = " << i->count() << "\n"; + + meta.AppendArrayItem(ns, i->tagName().c_str(), + kXMP_PropArrayIsOrdered, + i->toString(k).c_str()); + } + } + } + meta.SerializeToBuffer(&xmpPacket, kXMP_UseCompactFormat); + return 0; + } + catch (const XMP_Error& e) { +#ifndef SUPPRESS_WARNINGS + std::cerr << Error(39, e.GetID(), e.GetErrMsg()) << "\n"; +#endif + xmpPacket.clear(); + return 3; + }} // XmpParser::decode +#else + void XmpParser::encode( std::string& /*xmpPacket*/, + const XmpData& xmpData) + { + if (!xmpData.empty()) { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: XMP toolkit support not compiled in.\n"; +#endif + return 1; + } + } // XmpParser::encode +#endif // !EXV_HAVE_XMP_TOOLKIT + + // ************************************************************************* + // free functions + std::ostream& operator<<(std::ostream& os, const Xmpdatum& md) + { + return os << md.value(); + } + +} // namespace Exiv2 + +// ***************************************************************************** +// local definitions +namespace { + Exiv2::XmpKey::AutoPtr makeXmpKey(const std::string& schemaNs, + const std::string& propPath) + { + std::string property; + std::string::size_type idx = propPath.find(':'); + if (idx != std::string::npos) { + // Don't worry about out_of_range, XMP parser takes care of this + property = propPath.substr(idx + 1); + } + else { +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: Failed to determine property name from path " + << propPath << ", namespace " << schemaNs + << "; skipping property.\n"; +#endif + return Exiv2::XmpKey::AutoPtr(); + } + const char* prefix = Exiv2::XmpProperties::prefix(schemaNs); + if (prefix == 0) { +#ifndef SUPPRESS_WARNINGS + // Todo: Print warning only for the first property in each ns + std::cerr << "Warning: Unknown schema namespace " + << schemaNs << "; skipping property " + << property << ".\n"; +#endif + return Exiv2::XmpKey::AutoPtr(); + } + Exiv2::XmpKey::AutoPtr key; + // Todo: Avoid the try/catch block + try { + key = Exiv2::XmpKey::AutoPtr(new Exiv2::XmpKey(prefix, property)); + } + catch (const Exiv2::AnyError& e) { + // This should only happen for unknown property names +#ifndef SUPPRESS_WARNINGS + std::cerr << "Warning: " << e << "; skipping property.\n"; +#endif + } + return key; + } // makeXmpKey + +} diff --git a/src/xmp.hpp b/src/xmp.hpp new file mode 100644 index 00000000..36b30682 --- /dev/null +++ b/src/xmp.hpp @@ -0,0 +1,286 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004-2007 Andreas Huggel + * + * This program is part of the Exiv2 distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. + */ +/*! + @file xmp.hpp + @brief Encoding and decoding of XMP data + @version $Rev$ + @author Andreas Huggel (ahu) + ahuggel@gmx.net + @date 13-Jul-07, ahu: created + */ +#ifndef XMP_HPP_ +#define XMP_HPP_ + +// ***************************************************************************** +// included header files +#include "metadatum.hpp" +#include "types.hpp" +#include "value.hpp" +#include "properties.hpp" + +// + standard includes +#include +#include + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class definitions + + /*! + @brief Information related to an XMP property. An XMP metadatum consists + of an XmpKey and a Value and provides methods to manipulate these. + */ + class Xmpdatum : public Metadatum { + public: + //! @name Creators + //@{ + /*! + @brief Constructor for new tags created by an application. The + %Xmpdatum is created from a key / value pair. %Xmpdatum + copies (clones) the value if one is provided. Alternatively, a + program can create an 'empty' %Xmpdatum with only a key and + set the value using setValue(). + + @param key The key of the %Xmpdatum. + @param pValue Pointer to a %Xmpdatum value. + @throw Error if the key cannot be parsed and converted + to a known schema namespace prefix and property name. + */ + explicit Xmpdatum(const XmpKey& key, + const Value* pValue =0); + //! Copy constructor + Xmpdatum(const Xmpdatum& rhs); + //! Destructor + virtual ~Xmpdatum(); + //@} + + //! @name Manipulators + //@{ + //! Assignment operator + Xmpdatum& operator=(const Xmpdatum& rhs); + /*! + @brief Assign \em value to the %Xmpdatum. The type of the new Value + is set to UShortValue. + */ + Xmpdatum& operator=(const uint16_t& value); + /*! + @brief Assign \em value to the %Xmpdatum. + Calls setValue(const std::string&). + */ + Xmpdatum& operator=(const std::string& value); + /*! + @brief Assign \em value to the %Xmpdatum. + Calls setValue(const Value*). + */ + Xmpdatum& operator=(const Value& value); + void setValue(const Value* pValue); + /*! + @brief Set the value to the string \em value. Uses Value::read(const + std::string&). If the %Xmpdatum does not have a Value yet, + then a %Value of the correct type for this %Xmpdatum is + created. + */ + void setValue(const std::string& value); + //@} + + //! @name Accessors + //@{ + //! Not implemented. Calling this method will raise an exception. + long copy(byte* buf, ByteOrder byteOrder) const; + /*! + @brief Return the key of the Xmpdatum. The key is of the form + 'Xmp.prefix.property'. Note however that the + key is not necessarily unique, i.e., an XmpData object may + contain multiple metadata with the same key. + */ + std::string key() const; + //! Return the (preferred) schema namespace prefix. + std::string groupName() const; + //! Return the property name. + std::string tagName() const; + std::string tagLabel() const; + //! Properties don't have a tag number. Return 0. + uint16_t tag() const; + TypeId typeId() const; + const char* typeName() const; + // Todo: Remove this method from the baseclass + //! The Exif typeSize doesn't make sense here. Return 0. + long typeSize() const { return 0; } + long count() const; + long size() const; + std::string toString() const; + std::string toString(long n) const; + long toLong(long n =0) const; + float toFloat(long n =0) const; + Rational toRational(long n =0) const; + Value::AutoPtr getValue() const; + const Value& value() const; + //@} + + private: + // Pimpl idiom + struct Impl; + Impl* p_; + + }; // class Xmpdatum + + /*! + @brief Output operator for Xmpdatum types, printing the interpreted + tag value. + */ + std::ostream& operator<<(std::ostream& os, const Xmpdatum& md); + + //! Container type to hold all metadata + typedef std::vector XmpMetadata; + + /*! + @brief A container for XMP data. This is a top-level class of + the %Exiv2 library. + + Provide high-level access to the XMP data of an image: + - read XMP information from an XML block + - access metadata through keys and standard C++ iterators + - add, modify and delete metadata + - serialize XMP data to an XML block + */ + class XmpData { + public: + //! XmpMetadata iterator type + typedef XmpMetadata::iterator iterator; + //! XmpMetadata const iterator type + typedef XmpMetadata::const_iterator const_iterator; + + //! @name Manipulators + //@{ + /*! + @brief Returns a reference to the %Xmpdatum that is associated with a + particular \em key. If %XmpData does not already contain such + an %Xmpdatum, operator[] adds object \em Xmpdatum(key). + + @note Since operator[] might insert a new element, it can't be a const + member function. + */ + Xmpdatum& operator[](const std::string& key); + /*! + @brief Add an %Xmpdatum from the supplied key and value pair. This + method copies (clones) the value. + @return 0 if successful. + */ + int add(const XmpKey& key, Value* value); + /*! + @brief Add a copy of the Xmpdatum to the XMP metadata. + @return 0 if successful. + */ + int add(const Xmpdatum& xmpdatum); + /*! + @brief Delete the Xmpdatum at iterator position pos, return the + position of the next Xmpdatum. + + @note Iterators into the metadata, including pos, are potentially + invalidated by this call. + */ + iterator erase(iterator pos); + //! Delete all Xmpdatum instances resulting in an empty container. + void clear(); + //! Sort metadata by key + void sortByKey(); + //! Begin of the metadata + iterator begin(); + //! End of the metadata + iterator end(); + /*! + @brief Find the first Xmpdatum with the given key, return an iterator + to it. + */ + iterator findKey(const XmpKey& key); + //@} + + //! @name Accessors + //@{ + //! Begin of the metadata + const_iterator begin() const; + //! End of the metadata + const_iterator end() const; + /*! + @brief Find the first Xmpdatum with the given key, return a const + iterator to it. + */ + const_iterator findKey(const XmpKey& key) const; + //! Return true if there is no XMP metadata + bool empty() const; + //! Get the number of metadata entries + long count() const; + //@} + + private: + // DATA + XmpMetadata xmpMetadata_; + }; // class XmpData + + /*! + @brief Stateless parser class for XMP packets. Images use this + class to parse and serialize XMP packets. The parser uses + the XMP toolkit to do the job. + */ + class XmpParser { + public: + /*! + @brief Decode XMP metadata from an XMP packet \em xmpPacket into + \em xmpData. The format of the XMP packet must follow the + XMP specification. This method clears any previous contents + of \em xmpData. + + @param xmpData Container for the decoded XMP properties + @param xmpPacket The raw XMP packet to decode + @return 0 if successful;
+ 1 if XMP support has not been compiled-in;
+ 2 if the XMP toolkit failed to initialize;
+ 3 if the XMP toolkit failed and raised an XMP_Error + */ + static int decode( XmpData& xmpData, + const std::string& xmpPacket); + /*! + @brief Encode (serialize) XMP metadata from \em xmpData into a + string xmpPacket. The XMP packet returned in the string + follows the XMP specification. This method clears any + previous contents of \em xmpPacket. + + @param xmpPacket Reference to a string to hold the encoded XMP + packet. + @param xmpData XMP properties to encode. + @return 0 if successful;
+ 1 if XMP support has not been compiled-in;
+ 2 if the XMP toolkit failed to initialize;
+ 3 if the XMP toolkit failed and raised an XMP_Error + */ + static int encode( std::string& xmpPacket, + const XmpData& xmpData); + private: + static bool initialized_; //! Indicates if the XMP Toolkit has been initialized + + }; // class XmpParser + +} // namespace Exiv2 + +#endif // #ifndef XMP_HPP_ diff --git a/src/xmpdump.cpp b/src/xmpdump.cpp deleted file mode 100644 index 727bb8c9..00000000 --- a/src/xmpdump.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// ***************************************************************** -*- C++ -*- -// xmpdump.cpp, $Rev$ -// Sample program to dump the XMP packet of an image - -#include "image.hpp" -#include -#include -#include - -int main(int argc, char* const argv[]) -try { - - if (argc != 2) { - std::cout << "Usage: " << argv[0] << " file\n"; - return 1; - } - - Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(argv[1]); - assert(image.get() != 0); - image->readMetadata(); - - const std::string& xmpPacket = image->xmpPacket(); - if (xmpPacket.empty()) { - std::string error(argv[1]); - error += ": No XMP packet found in the file"; - throw Exiv2::Error(1, error); - } - std::cout << xmpPacket << "\n"; - - return 0; -} -catch (Exiv2::AnyError& e) { - std::cout << "Caught Exiv2 exception '" << e << "'\n"; - return -1; -} diff --git a/src/xmpparse.cpp b/src/xmpparse.cpp new file mode 100644 index 00000000..bb456426 --- /dev/null +++ b/src/xmpparse.cpp @@ -0,0 +1,51 @@ +// ***************************************************************** -*- C++ -*- +// xmpparse.cpp, $Rev$ +// Read an XMP packet from a file, parse it and print all (known) properties. + +#include "basicio.hpp" +#include "xmp.hpp" +#include "error.hpp" + +#include +#include +#include + +int main(int argc, char* const argv[]) +try { + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " file\n"; + return 1; + } + Exiv2::DataBuf buf = Exiv2::readFile(argv[1]); + std::string xmpPacket; + xmpPacket.assign(reinterpret_cast(buf.pData_), buf.size_); + Exiv2::XmpData xmpData; + if (0 != Exiv2::XmpParser::decode(xmpData, xmpPacket)) { + std::string error(argv[1]); + error += ": Failed to parse file contents (XMP packet)"; + throw Exiv2::Error(1, error); + } + if (xmpData.empty()) { + std::string error(argv[1]); + error += ": No XMP properties found in the XMP packet"; + throw Exiv2::Error(1, error); + } + for (Exiv2::XmpData::const_iterator md = xmpData.begin(); + md != xmpData.end(); ++md) { + std::cout << std::setfill(' ') << std::left + << std::setw(44) + << md->key() << " " + << std::setw(9) << std::setfill(' ') << std::left + << md->typeName() << " " + << std::dec << std::setw(3) + << std::setfill(' ') << std::right + << md->count() << " " + << std::dec << md->value() + << std::endl; + } + return 0; +} +catch (Exiv2::AnyError& e) { + std::cout << "Caught Exiv2 exception '" << e << "'\n"; + return -1; +} diff --git a/src/xmpparser-test.cpp b/src/xmpparser-test.cpp new file mode 100644 index 00000000..fad92eda --- /dev/null +++ b/src/xmpparser-test.cpp @@ -0,0 +1,68 @@ +// ***************************************************************** -*- C++ -*- +// xmpparser-test.cpp, $Rev$ +// Read an XMP packet from a file, parse and re-serialize it. + +#include "basicio.hpp" +#include "xmp.hpp" +#include "error.hpp" +#include "futils.hpp" + +#include +#include +#include + +int main(int argc, char* const argv[]) +try { + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " file\n"; + return 1; + } + std::string filename(argv[1]); + Exiv2::DataBuf buf = Exiv2::readFile(filename); + std::string xmpPacket; + xmpPacket.assign(reinterpret_cast(buf.pData_), buf.size_); + Exiv2::XmpData xmpData; + if (0 != Exiv2::XmpParser::decode(xmpData, xmpPacket)) { + std::string error(argv[1]); + error += ": Failed to parse file contents (XMP packet)"; + throw Exiv2::Error(1, error); + } + if (xmpData.empty()) { + std::string error(argv[1]); + error += ": No XMP properties found in the XMP packet"; + throw Exiv2::Error(1, error); + } + for (Exiv2::XmpData::const_iterator md = xmpData.begin(); + md != xmpData.end(); ++md) { + std::cout << std::setfill(' ') << std::left + << std::setw(44) + << md->key() << " " + << std::setw(9) << std::setfill(' ') << std::left + << md->typeName() << " " + << std::dec << std::setw(3) + << std::setfill(' ') << std::right + << md->count() << " " + << std::dec << md->value() + << std::endl; + } + std::cerr << "-----------------------------------------------\n"; + if (0 != Exiv2::XmpParser::encode(xmpPacket, xmpData)) { + std::string error(argv[1]); + error += ": Failed to encode the XMP data"; + throw Exiv2::Error(1, error); + } + filename += "-new"; + Exiv2::FileIo file(filename); + if (file.open("wb") != 0) { + throw Exiv2::Error(10, filename, "wb", Exiv2::strError()); + } + if (file.write(reinterpret_cast(xmpPacket.data()), xmpPacket.size()) == 0) { + throw Exiv2::Error(2, filename, Exiv2::strError(), "FileIo::write"); + } + + return 0; +} +catch (Exiv2::AnyError& e) { + std::cout << "Caught Exiv2 exception '" << e << "'\n"; + return -1; +} diff --git a/test/data/exiv2-test.out b/test/data/exiv2-test.out index 92fdc18f..51363b95 100644 --- a/test/data/exiv2-test.out +++ b/test/data/exiv2-test.out @@ -3,7 +3,7 @@ tmp/ Exiv2 version ------------------------------------------------------------ ../../src/exiv2 -exiv2 0.15 +exiv2 0.16 Copyright (C) 2004-2007 Andreas Huggel. This program is free software; you can redistribute it and/or @@ -33,12 +33,12 @@ Actions: rm | delete Delete image metadata from the files. in | insert Insert metadata from corresponding *.exv files. Use option -S to change the suffix of the input files. - ex | extract Extract metadata to *.exv and thumbnail image files. + ex | extract Extract metadata to *.exv, *.xmp and thumbnail image files. mv | rename Rename files and/or set file timestamps according to the Exif create timestamp. The filename format can be set with -r format, timestamp options are controlled with -t and -T. mo | modify Apply commands to modify (add, set, delete) the Exif and - Iptc metadata of image files or set the Jpeg comment. + IPTC metadata of image files or set the JPEG comment. Requires option -c, -m or -M. fi | fixiso Copy ISO setting from the Nikon Makernote to the regular Exif tag. @@ -62,8 +62,9 @@ Options: t : interpreted (translated) Exif data (shortcut for -Pkyct) v : plain Exif data values (shortcut for -Pxgnycv) h : hexdump of the Exif data (shortcut for -Pxgnycsh) - i : Iptc data values - c : Jpeg comment + i : IPTC data values + x : XMP properties + c : JPEG comment -P cols Print columns for the Exif taglist ('print' action). Valid are: x : print a column with the tag value g : group name @@ -80,20 +81,24 @@ Options: a : all supported metadata (the default) e : Exif section t : Exif thumbnail only - i : Iptc data - c : Jpeg comment + i : IPTC data + x : XMP packet + c : JPEG comment -i tgt Insert target(s) for the 'insert' action. Possible targets are - the same as those for the -d option. Only Jpeg thumbnails can - be inserted, they need to be named -thumb.jpg + the same as those for the -d option, plus: + X : Insert XMP packet from .xmp + Only JPEG thumbnails can be inserted, they need to be named + -thumb.jpg -e tgt Extract target(s) for the 'extract' action. Possible targets - are the same as those for the -d option. + are the same as those for the -i option, plus: + X : Extract XMP packet to .xmp -r fmt Filename format for the 'rename' action. The format string follows strftime(3). The following keywords are supported: :basename: - original filename without extension :dirname: - name of the directory holding the original file :parentname: - name of parent directory Default filename format is %Y%m%d_%H%M%S. - -c txt Jpeg comment string to set in the image. + -c txt JPEG comment string to set in the image. -m file Command file for the modify action. The format for commands is set|add|del [[] ]. -M cmd Command line for the modify action. The format for the @@ -1955,7 +1960,7 @@ File 6/15: 20030925_201850.jpg Writing Exif data from 20030925_201850.jpg to ./20030925_201850.exv File 7/15: 20001026_044550.jpg Writing Exif data from 20001026_044550.jpg to ./20001026_044550.exv -Writing Jpeg comment from 20001026_044550.jpg to ./20001026_044550.exv +Writing JPEG comment from 20001026_044550.jpg to ./20001026_044550.exv File 8/15: 20030926_111535.jpg Writing Exif data from 20030926_111535.jpg to ./20030926_111535.exv File 9/15: 20040316_075137.jpg @@ -4866,7 +4871,7 @@ File 6/15: 20030925_201850.jpg Erasing Exif data from the file File 7/15: 20001026_044550.jpg Erasing Exif data from the file -Erasing Jpeg comment from the file +Erasing JPEG comment from the file File 8/15: 20030926_111535.jpg Erasing Exif data from the file File 9/15: 20040316_075137.jpg @@ -4929,7 +4934,7 @@ File 6/15: 20030925_201850.jpg Writing Exif data from ./20030925_201850.exv to 20030925_201850.jpg File 7/15: 20001026_044550.jpg Writing Exif data from ./20001026_044550.exv to 20001026_044550.jpg -Writing Jpeg comment from ./20001026_044550.exv to 20001026_044550.jpg +Writing JPEG comment from ./20001026_044550.exv to 20001026_044550.jpg File 8/15: 20030926_111535.jpg Writing Exif data from ./20030926_111535.exv to 20030926_111535.jpg File 9/15: 20040316_075137.jpg