Intermediate state: Merged rev. 1153-1196 from branches/xmp.

This commit is contained in:
Andreas Huggel 2007-09-16 09:30:30 +00:00
parent 77bec2c60e
commit ac314ddfbc
39 changed files with 3506 additions and 506 deletions

77
README-XMP Normal file
View File

@ -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.<Prefix>.<Property>", where <Prefix> is the preferred
(or rather, registered) prefix for a schema namespace and
<Property> 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

View File

@ -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@

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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

View File

@ -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_

View File

@ -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)"))
};

View File

@ -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;
}
}

View File

@ -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 <EM>n</EM>-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 <EM>n</EM>-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 <EM>n</EM>-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.
<b>Example:</b> <br>
@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:

View File

@ -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 <n> 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<whatever>\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 <file>.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 <file>.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 <ahuggel@gmx.net>.
was written by Andreas Huggel <ahuggel@gmx.net>.
.PP
This manual page was originally written by KELEMEN Peter <fuji@debian.org>,
for the Debian project.

View File

@ -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 <file>-thumb.jpg\n")
" the same as those for the -d option, plus:\n"
" X : Insert XMP packet from <file>.xmp\n"
" Only JPEG thumbnails can be inserted, they need to be named\n"
" <file>-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 <file>.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 <key> [[<type>] <value>].\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";

View File

@ -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),

View File

@ -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 <sys/types.h> does not define pid_t. */
#define pid_t int

View File

@ -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;

View File

@ -40,6 +40,7 @@
#include "basicio.hpp"
#include "exif.hpp"
#include "iptc.hpp"
#include "xmp.hpp"
// + standard includes
#include <string>
@ -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_;
};

View File

@ -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
'<b>Iptc</b>.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.
<b>Example:</b> <br>
@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;
//@}

View File

@ -291,7 +291,12 @@ namespace Exiv2 {
io_->read(xmpPacket.pData_, xmpPacket.size_);
if (io_->error() || io_->eof()) throw Error(14);
xmpPacket_.assign(reinterpret_cast<char*>(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_

View File

@ -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 <EM>n</EM>-th component of the value converted to
a string. The behaviour of the method is undefined if there
is no <EM>n</EM>-th component.
*/
virtual std::string toString(long n) const =0;
/*!
@brief Return the <EM>n</EM>-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 <EM>n</EM>-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 <EM>n</EM>-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 <EM>n</EM>-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 <EM>n</EM>-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 <EM>n</EM>-th component.
*/
virtual Rational toRational(long n =0) const =0;
/*!

View File

@ -24,13 +24,13 @@
<a href="http://www.dalibor.cz/minolta/makernote.htm">Minolta Makernote Format Specification</a> by Dalibor Jelinek,<br>
<a href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Minolta.html">Minolta Makernote list</a> by Phil Harvey<br>
<a href="http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html">Minolta Makernote list from PHP JPEG Metadata Toolkit</a><br>
Email communication with <a href="mailto:caulier dot gilles at kdemail dot net">caulier dot gilles at kdemail dot net</a><br>
Email communication with <a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a><br>
Some Minolta camera settings have been decoded by <a href="mailto:xraynaud@gmail.com">Xavier Raynaud</a> from digiKam project and added by Gilles Caulier.
@version $Rev$
@author Andreas Huggel (ahu)
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
@author Gilles Caulier (gc)
<a href="mailto:caulier dot gilles at kdemail dot net">caulier dot gilles at kdemail dot net</a>
<a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
@date 06-May-06, gc: submitted
*/
#ifndef MINOLTAMN_HPP_

View File

@ -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<char*>(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<char*>(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<char*>(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

View File

@ -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:<br>
<a href="http://www.vias.org/pngguide/chapter11_05.html">PNG iTXt chunk structure</a> from PNG definitive guide,<br>
<a href="http://www.vias.org/pngguide/chapter11_04.html">PNG tTXt and zTXt chunks structures</a> from PNG definitive guide,<br>
<a href="http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html">PNG tags list</a> by Phil Harvey<br>
Email communication with <a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a><br>
@version $Rev: 823 $
@author Andreas Huggel (ahu)
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
@author Gilles Caulier (gc)
<a href="mailto:caulier dot gilles at kdemail dot net">caulier dot gilles at kdemail dot net</a>
<a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
@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

View File

@ -27,7 +27,7 @@
@author Andreas Huggel (ahu)
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
@author Gilles Caulier (gc)
<a href="mailto:caulier dot gilles at kdemail dot net">caulier dot gilles at kdemail dot net</a>
<a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
@date 12-Jun-06, gc: submitted
*/
#ifndef PNGIMAGE_HPP_

906
src/properties.cpp Normal file
View File

@ -0,0 +1,906 @@
// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2007 Andreas Huggel <ahuggel@gmx.net>
*
* 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) <ahuggel@gmx.net>
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 <iostream>
#include <iomanip>
#include <sstream>
// *****************************************************************************
// 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

262
src/properties.hpp Normal file
View File

@ -0,0 +1,262 @@
// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2007 Andreas Huggel <ahuggel@gmx.net>
*
* 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.<BR>References:<BR>
<a href="http://www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf">XMP Specification</a> from Adobe
<I>(Property descriptions copied from the XMP specification with the permission of Adobe)</I>
@version $Rev$
@author Andreas Huggel (ahu)
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
@date 13-Jul-07, ahu: created
*/
#ifndef PROPERTIES_HPP_
#define PROPERTIES_HPP_
// *****************************************************************************
// included header files
#include "types.hpp"
#include "metadatum.hpp"
// + standard includes
#include <string>
#include <iosfwd>
#include <memory>
// *****************************************************************************
// 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<XmpKey> 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 '<b>Xmp</b>' 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_

View File

@ -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;
}

View File

@ -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<const char*>(pData), size));
std::string& xmpPacket = pImage_->xmpPacket();
xmpPacket.assign(reinterpret_cast<const char*>(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

View File

@ -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)
};

View File

@ -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<typename T>
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)

View File

@ -44,6 +44,7 @@ EXIV2_RCSID("@(#) $Id$")
#include <cstring>
#include <ctime>
#include <cstdlib>
#include <ctype.h>
// *****************************************************************************
// 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<byte>(tmp));
}
return 0;
@ -177,6 +188,13 @@ namespace Exiv2 {
return os;
}
std::string DataValue::toString(long n) const
{
std::ostringstream os;
os << static_cast<int>(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<const char*>(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<std::string>::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<long>(value_[n], ok);
}
float XmpTextValue::toFloat(long n) const
{
bool ok;
return stringTo<float>(value_[n], ok);
}
Rational XmpTextValue::toRational(long n) const
{
bool ok;
return stringTo<Rational>(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

View File

@ -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 <EM>n</EM>-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 <EM>n</EM>-th
component.
*/
virtual std::string toString(long n) const;
/*!
@brief Convert the <EM>n</EM>-th component of the value to a long.
The behaviour of this method may be undefined if there is no
<EM>n</EM>-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 <EM>n</EM>-th component of the value to a float.
The behaviour of this method may be undefined if there is no
<EM>n</EM>-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 <EM>n</EM>-th component of the value to a Rational.
The behaviour of this method may be undefined if there is no
<EM>n</EM>-th component.
@return The converted value.
*/
@ -212,6 +219,7 @@ namespace Exiv2 {
<TR><TD class="indexkey">date</TD><TD class="indexvalue">%DateValue</TD></TR>
<TR><TD class="indexkey">time</TD><TD class="indexvalue">%TimeValue</TD></TR>
<TR><TD class="indexkey">comment</TD><TD class="indexvalue">%CommentValue</TD></TR>
<TR><TD class="indexkey">xmpText</TD><TD class="indexvalue">%XmpTextValue</TD></TR>
<TR><TD class="indexkey"><EM>default:</EM></TD><TD class="indexvalue">%DataValue(typeId)</TD></TR>
</TABLE>
@ -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 <EM>n</EM>-th component of the value as a string.
The behaviour of this method may be undefined if there is no
<EM>n</EM>-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<XmpTextValue> 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<long>(value_.size()); }
virtual long size() const;
/*!
@brief Return the <EM>n</EM>-th component of the value as a string.
The behaviour of this method may be undefined if there is no
<EM>n</EM>-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<std::string> 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<float>(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<float>(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<typename T> TypeId getType();
@ -858,7 +972,7 @@ namespace Exiv2 {
template<> inline TypeId getType<Rational>() { return signedRational; }
// No default implementation: let the compiler/linker complain
// template<typename T> inline TypeId getType() { return invalid; }
// template<typename T> 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<long>(value_.size()); }
virtual long size() const;
virtual std::ostream& write(std::ostream& os) const;
/*!
@brief Return the <EM>n</EM>-th component of the value as a string.
The behaviour of this method may be undefined if there is no
<EM>n</EM>-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<Rational> 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<typename T>
inline std::string ValueType<T>::toString(long n) const
{
return Exiv2::toString(value_[n]);
}
// Default implementation
template<typename T>
inline long ValueType<T>::toLong(long n) const
@ -1264,7 +1407,6 @@ namespace Exiv2 {
sizeDataArea_ = len;
return 0;
}
} // namespace Exiv2
#endif // #ifndef VALUE_HPP_

View File

@ -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.
*/

558
src/xmp.cpp Normal file
View File

@ -0,0 +1,558 @@
// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2007 Andreas Huggel <ahuggel@gmx.net>
*
* 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) <ahuggel@gmx.net>
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 <iostream>
#include <algorithm>
#include <cassert>
#include <string>
// Adobe XMP Toolkit
#ifdef EXV_HAVE_XMP_TOOLKIT
# define TXMP_STRING_TYPE std::string
# include <XMP.hpp>
# include <XMP.incl_cpp>
#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<long>(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
}

286
src/xmp.hpp Normal file
View File

@ -0,0 +1,286 @@
// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2004-2007 Andreas Huggel <ahuggel@gmx.net>
*
* 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)
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
@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 <string>
#include <vector>
// *****************************************************************************
// 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
'<b>Xmp</b>.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<Xmpdatum> 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;<BR>
1 if XMP support has not been compiled-in;<BR>
2 if the XMP toolkit failed to initialize;<BR>
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;<BR>
1 if XMP support has not been compiled-in;<BR>
2 if the XMP toolkit failed to initialize;<BR>
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_

View File

@ -1,35 +0,0 @@
// ***************************************************************** -*- C++ -*-
// xmpdump.cpp, $Rev$
// Sample program to dump the XMP packet of an image
#include "image.hpp"
#include <string>
#include <iostream>
#include <cassert>
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;
}

51
src/xmpparse.cpp Normal file
View File

@ -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 <string>
#include <iostream>
#include <iomanip>
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<char*>(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;
}

68
src/xmpparser-test.cpp Normal file
View File

@ -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 <string>
#include <iostream>
#include <iomanip>
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<char*>(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<const Exiv2::byte*>(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;
}

View File

@ -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 <file>-thumb.jpg
the same as those for the -d option, plus:
X : Insert XMP packet from <file>.xmp
Only JPEG thumbnails can be inserted, they need to be named
<file>-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 <file>.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 <key> [[<type>] <value>].
-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