libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

commit 8aca9dac797109fec1234bc99cdc87ad439d34d2
parent cf2f5a28f4537e1507606d083fc649289610b733
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  3 Jul 2005 19:43:52 +0000

adding exiv2

Diffstat:
Mconfigure.ac | 1+
Msrc/plugins/Makefile.am | 5-----
Asrc/plugins/exiv2/actions.cpp | 1331+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/actions.hpp | 328+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/addmoddel.cpp | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/basicio.cpp | 500+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/basicio.hpp | 642+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/canonmn.cpp | 934+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/canonmn.hpp | 238+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/datasets.cpp | 392+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/datasets.hpp | 358+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/doxygen.hpp | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/error.cpp | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/error.hpp | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/exif.cpp | 1230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/exif.hpp | 909+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/exifcomment.cpp | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/exifprint.cpp | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/exiv2.cpp | 804+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/exiv2.hpp | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/plugins/exiv2extractor.cc -> src/plugins/exiv2/exiv2extractor.cc | 0
Asrc/plugins/exiv2/fujimn.cpp | 278+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/fujimn.hpp | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/futils.cpp | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/futils.hpp | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/ifd.cpp | 719+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/ifd.hpp | 601+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/image.cpp | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/image.hpp | 461+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/iptc.cpp | 298+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/iptc.hpp | 416+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/iptceasy.cpp | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/iptcprint.cpp | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/jpgimage.cpp | 661+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/jpgimage.hpp | 405+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/makernote.cpp | 422+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/makernote.hpp | 474+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/metacopy.cpp | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/metacopy.hpp | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/metadatum.cpp | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/metadatum.hpp | 294+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/mn.hpp | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/nikonmn.cpp | 870+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/nikonmn.hpp | 309+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/olympusmn.cpp | 318+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/olympusmn.hpp | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/panasonicmn.cpp | 361+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/panasonicmn.hpp | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/rcsid.hpp | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/sigmamn.cpp | 211+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/sigmamn.hpp | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/sonymn.cpp | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/sonymn.hpp | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/taglist.cpp | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/tags.cpp | 1230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/tags.hpp | 444+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/types.cpp | 343+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/types.hpp | 307+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/utils.cpp | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/utils.hpp | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/value.cpp | 557+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/plugins/exiv2/value.hpp | 1225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
62 files changed, 21939 insertions(+), 5 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -279,6 +279,7 @@ src/plugins/rpm/Makefile src/plugins/printable/Makefile src/plugins/hash/Makefile src/plugins/thumbnail/Makefile +src/plugins/exiv2/Makefile src/test/Makefile ]) diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am @@ -62,7 +62,6 @@ plugin_LTLIBRARIES = \ libextractor_zip.la \ libextractor_lower.la \ libextractor_translit.la \ - libextractor_exiv2.la \ $(extraqt) \ $(extraogg) @@ -253,7 +252,3 @@ libextractor_translit_la_SOURCES = \ libextractor_translit_la_LDFLAGS = \ $(PLUGINFLAGS) -libextractor_exiv2_la_SOURCES = \ - exiv2extractor.cc -libextractor_exiv2_la_LDFLAGS = \ - -lexiv2 $(PLUGINFLAGS) diff --git a/src/plugins/exiv2/actions.cpp b/src/plugins/exiv2/actions.cpp @@ -0,0 +1,1331 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: actions.cpp + Version: $Rev: 583 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 08-Dec-03, ahu: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: actions.cpp 583 2005-06-12 06:41:40Z ahuggel $"); + +// ***************************************************************************** +// included header files +#ifdef _MSC_VER +# include "exv_msvc.h" +#else +# include "exv_conf.h" +#endif + +#include "actions.hpp" +#include "image.hpp" +#include "exiv2.hpp" +#include "utils.hpp" +#include "types.hpp" +#include "exif.hpp" +#include "canonmn.hpp" +#include "iptc.hpp" +#include "futils.hpp" +#ifndef EXV_HAVE_TIMEGM +# include "timegm.h" +#endif + +// + standard includes +#include <string> +#include <iostream> +#include <iomanip> +#include <fstream> +#include <sstream> +#include <cstring> +#include <cstdio> +#include <ctime> +#include <cmath> +#include <cassert> +#include <sys/types.h> // for stat() +#include <sys/stat.h> // for stat() +#ifdef EXV_HAVE_UNISTD_H +# include <unistd.h> // for stat() +#endif + +// ***************************************************************************** +// local declarations +namespace { + + // Convert a string "YYYY:MM:DD HH:MI:SS" to a struct tm type, + // returns 0 if successful + int str2Tm(const std::string& timeStr, struct tm* tm); + + // Convert a string "YYYY:MM:DD HH:MI:SS" to a UTC time, -1 on error + time_t str2Time(const std::string& timeStr); + + // Convert a UTC time to a string "YYYY:MM:DD HH:MI:SS", "" on error + std::string time2Str(time_t time); + + /*! + @brief Copy metadata from source to target according to Params::copyXyz + + @param source Source file path + @param target Target file path. An *.exv file is created if target doesn't + exist. + @param preserve Indicates if existing metadata in the target file should + be kept. + @return 0 if successful, else an error code + */ + int metacopy(const std::string& source, + const std::string& target, + bool preserve); +} + +// ***************************************************************************** +// class member definitions +namespace Action { + + Task::AutoPtr Task::clone() const + { + return AutoPtr(clone_()); + } + + TaskFactory* TaskFactory::instance_ = 0; + + TaskFactory& TaskFactory::instance() + { + if (0 == instance_) { + instance_ = new TaskFactory; + } + return *instance_; + } // TaskFactory::instance + + void TaskFactory::registerTask(TaskType type, Task::AutoPtr task) + { + Registry::iterator i = registry_.find(type); + if (i != registry_.end()) { + delete i->second; + } + registry_[type] = task.release(); + } // TaskFactory::registerTask + + TaskFactory::TaskFactory() + { + // Register a prototype of each known task + registerTask(adjust, Task::AutoPtr(new Adjust)); + registerTask(print, Task::AutoPtr(new Print)); + registerTask(rename, Task::AutoPtr(new Rename)); + registerTask(erase, Task::AutoPtr(new Erase)); + registerTask(extract, Task::AutoPtr(new Extract)); + registerTask(insert, Task::AutoPtr(new Insert)); + registerTask(modify, Task::AutoPtr(new Modify)); + } // TaskFactory c'tor + + Task::AutoPtr TaskFactory::create(TaskType type) + { + Registry::const_iterator i = registry_.find(type); + if (i != registry_.end() && i->second != 0) { + Task* t = i->second; + return t->clone(); + } + return Task::AutoPtr(0); + } // TaskFactory::create + + int Print::run(const std::string& path) + try { + path_ = path; + int rc = 0; + switch (Params::instance().printMode_) { + case Params::pmSummary: rc = printSummary(); break; + case Params::pmInterpreted: rc = printInterpreted(); break; + case Params::pmValues: rc = printValues(); break; + case Params::pmHexdump: rc = printHexdump(); break; + case Params::pmIptc: rc = printIptc(); break; + case Params::pmComment: rc = printComment(); break; + } + return rc; + } + catch(const Exiv2::AnyError& e) { + std::cerr << "Exiv2 exception in print action for file " + << path << ":\n" << e << "\n"; + return 1; + } // Print::run + + int Print::printSummary() + { + 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::ExifData &exifData = image->exifData(); + if (exifData.empty()) { + std::cerr << path_ + << ": No Exif data found in the file\n"; + return -3; + } + align_ = 16; + + // Filename + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Filename" << ": " << path_ << std::endl; + + // Filesize + struct stat buf; + if (0 == stat(path_.c_str(), &buf)) { + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Filesize" << ": " << buf.st_size << " Bytes" + << std::endl; + } + + // Camera make + printTag(exifData, "Exif.Image.Make", "Camera make"); + + // Camera model + printTag(exifData, "Exif.Image.Model", "Camera model"); + + // Image Timestamp + printTag(exifData, "Exif.Photo.DateTimeOriginal", "Image timestamp"); + + // Image number + // Todo: Image number for cameras other than Canon + printTag(exifData, "Exif.Canon.ImageNumber", "Image number"); + + // Exposure time + // From ExposureTime, failing that, try ShutterSpeedValue + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Exposure time" << ": "; + Exiv2::ExifData::const_iterator md; + if (0 == printTag(exifData, "Exif.Photo.ExposureTime")) { + md = exifData.findKey( + Exiv2::ExifKey("Exif.Photo.ShutterSpeedValue")); + if (md != exifData.end()) { + double tmp = exp(log(2.0) * md->toFloat()) + 0.5; + if (tmp > 1) { + std::cout << "1/" << static_cast<long>(tmp) << " s"; + } + else { + std::cout << static_cast<long>(1/tmp) << " s"; + } + } + } + std::cout << std::endl; + + // Aperture + // Get if from FNumber and, failing that, try ApertureValue + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Aperture" << ": "; + if (0 == printTag(exifData, "Exif.Photo.FNumber")) { + md = exifData.findKey( + Exiv2::ExifKey("Exif.Photo.ApertureValue")); + if (md != exifData.end()) { + std::cout << std::fixed << std::setprecision(1) + << "F" << exp(log(2.0) * md->toFloat() / 2); + } + } + std::cout << std::endl; + + // Exposure bias + printTag(exifData, "Exif.Photo.ExposureBiasValue", "Exposure bias"); + + // Flash + printTag(exifData, "Exif.Photo.Flash", "Flash"); + + // Todo: Flash bias, flash energy + // Todo: Implement this for other cameras + bool done = false; + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Flash bias" << ": "; + if (!done) { + done = 0 != printTag(exifData, "Exif.CanonCs2.FlashBias"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Panasonic.FlashBias"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Olympus.FlashBias"); + } + std::cout << std::endl; + + // Actual focal length and 35 mm equivalent + // Todo: Calculate 35 mm equivalent a la jhead + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Focal length" << ": "; + if (1 == printTag(exifData, "Exif.Photo.FocalLength")) { + md = exifData.findKey( + Exiv2::ExifKey("Exif.Photo.FocalLengthIn35mmFilm")); + if (md != exifData.end()) { + std::cout << " (35 mm equivalent: " << *md << ")"; + } + } + std::cout << std::endl; + + // Subject distance + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Subject distance" << ": "; + done = false; + if (!done) { + done = 0 != printTag(exifData, "Exif.Photo.SubjectDistance"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.CanonCs2.SubjectDistance"); + } + std::cout << std::endl; + + // ISO speed + // from ISOSpeedRatings or the Makernote + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "ISO speed" << ": "; + done = false; + if (!done) { + done = 0 != printTag(exifData, "Exif.Photo.ISOSpeedRatings"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.CanonCs1.ISOSpeed"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Nikon1.ISOSpeed"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Nikon2.ISOSpeed"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Nikon3.ISOSpeed"); + } + std::cout << std::endl; + + // Exposure mode + // From ExposureProgram or Canon Makernote + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Exposure mode" << ": "; + done = false; + if (!done) { + done = 0 != printTag(exifData, "Exif.Photo.ExposureProgram"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.CanonCs1.ExposureProgram"); + } + std::cout << std::endl; + + // Metering mode + printTag(exifData, "Exif.Photo.MeteringMode", "Metering mode"); + + // Macro mode + // Todo: Implement this for other cameras + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Macro mode" << ": "; + done = false; + if (!done) { + done = 0 != printTag(exifData, "Exif.CanonCs1.Macro"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Fujifilm.Macro"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Olympus.Macro"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Panasonic.Macro"); + } + std::cout << std::endl; + + // Image quality setting (compression) + // Todo: Implement this for other cameras + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Image quality" << ": "; + done = false; + if (!done) { + done = 0 != printTag(exifData, "Exif.CanonCs1.Quality"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Fujifilm.Quality"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Sigma.Quality"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Nikon1.Quality"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Nikon2.Quality"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Nikon3.Quality"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Olympus.Quality"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Panasonic.Quality"); + } + std::cout << std::endl; + + // Exif Resolution + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Exif Resolution" << ": "; + long xdim = 0; + long ydim = 0; + md = exifData.findKey(Exiv2::ExifKey("Exif.Photo.PixelXDimension")); + if (md != exifData.end()) xdim = md->toLong(); + md = exifData.findKey(Exiv2::ExifKey("Exif.Photo.PixelYDimension")); + if (md != exifData.end()) ydim = md->toLong(); + if (xdim != 0 && ydim != 0) { + std::cout << xdim << " x " << ydim; + } + std::cout << std::endl; + + // White balance + // Todo: Implement this for other cameras + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "White balance" << ": "; + done = false; + if (!done) { + done = 0 != printTag(exifData, "Exif.CanonCs2.WhiteBalance"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Fujifilm.WhiteBalance"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Sigma.WhiteBalance"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Nikon1.WhiteBalance"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Nikon2.WhiteBalance"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Nikon3.WhiteBalance"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Olympus.WhiteBalance"); + } + if (!done) { + done = 0 != printTag(exifData, "Exif.Panasonic.WhiteBalance"); + } + std::cout << std::endl; + + // Thumbnail + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Thumbnail" << ": "; + std::string thumbExt = exifData.thumbnailExtension(); + if (thumbExt.empty()) { + std::cout << "None"; + } + else { + Exiv2::DataBuf buf = exifData.copyThumbnail(); + std::cout << exifData.thumbnailFormat() << ", " + << buf.size_ << " Bytes"; + } + std::cout << std::endl; + + // Copyright + printTag(exifData, "Exif.Image.Copyright", "Copyright"); + + // Exif Comment + printTag(exifData, "Exif.Photo.UserComment", "Exif comment"); + std::cout << std::endl; + + return 0; + } // Print::printSummary + + int Print::printTag(const Exiv2::ExifData& exifData, + const std::string& key, + const std::string& label) const + { + int rc = 0; + if (!label.empty()) { + // Print the label in any case for the moment (to see what's missing) + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << label << ": "; + } + Exiv2::ExifKey ek(key); + Exiv2::ExifData::const_iterator md = exifData.findKey(ek); + if (md != exifData.end()) { + std::cout << *md; + rc = 1; + } + if (!label.empty()) std::cout << std::endl; + return rc; + } // Print::printTag + + int Print::printInterpreted() + { + 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::ExifData &exifData = image->exifData(); + if (exifData.empty()) { + std::cerr << path_ + << ": No Exif data found in the file\n"; + return -3; + } + Exiv2::ExifData::const_iterator md; + for (md = exifData.begin(); md != exifData.end(); ++md) { + std::cout << std::setw(44) << std::setfill(' ') << std::left + << 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 + << std::endl; + } + + return 0; + } // Print::printInterpreted + + int Print::printValues() + { + 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::ExifData &exifData = image->exifData(); + if (exifData.empty()) { + std::cerr << path_ + << ": No Exif data found in the file\n"; + return -3; + } + Exiv2::ExifData::const_iterator end = exifData.end(); + Exiv2::ExifData::const_iterator md; + for (md = exifData.begin(); md != end; ++md) { + std::cout << "0x" << std::setw(4) << std::setfill('0') << std::right + << std::hex << md->tag() << " " + << std::setw(9) << std::setfill(' ') << std::left + << md->ifdName() << " " + << std::setw(27) << std::setfill(' ') << std::left + << md->tagName() << " " + << 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::printValues + + int Print::printIptc() + { + 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::IptcData &iptcData = image->iptcData(); + if (iptcData.empty()) { + std::cerr << path_ + << ": No Iptc data found in the file\n"; + return -3; + } + Exiv2::IptcData::const_iterator end = iptcData.end(); + Exiv2::IptcData::const_iterator md; + for (md = iptcData.begin(); md != end; ++md) { + std::cout << std::setw(44) << std::setfill(' ') << std::left + << 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::printIptc + + int Print::printHexdump() + { + 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::ExifData &exifData = image->exifData(); + if (exifData.empty()) { + std::cerr << path_ + << ": No Exif data found in the file\n"; + return -3; + } + Exiv2::ExifData::const_iterator md; + for (md = exifData.begin(); md != exifData.end(); ++md) { + std::cout << std::setw(4) << std::setfill(' ') << std::left + << md->ifdName() << " " + << "0x" << std::setw(4) << std::setfill('0') << std::right + << std::hex << md->tag() << " " + << std::setw(9) << std::setfill(' ') << std::left + << md->typeName() << " " + << std::dec << std::setw(3) + << std::setfill(' ') << std::right + << md->count() << " " + << std::dec << std::setw(3) + << std::setfill(' ') << std::right + << md->size() << " " + << std::setw(27) << std::setfill(' ') << std::left + << md->tagName() << std::endl; + Exiv2::DataBuf buf(md->size()); + md->copy(buf.pData_, exifData.byteOrder()); + Exiv2::hexdump(std::cout, buf.pData_, buf.size_); + } + + return 0; + } // Print::printHexdump + + int Print::printComment() + { + 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(); + if (Params::instance().verbose_) { + std::cout << "Jpeg comment: "; + } + std::cout << image->comment() << std::endl; + return 0; + } // Print::printComment + + Print::AutoPtr Print::clone() const + { + return AutoPtr(clone_()); + } + + Print* Print::clone_() const + { + return new Print(*this); + } + + int Rename::run(const std::string& path) + try { + 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::ExifData &exifData = image->exifData(); + if (exifData.empty()) { + std::cerr << path + << ": No Exif data found in the file\n"; + return -3; + } + Exiv2::ExifKey key("Exif.Photo.DateTimeOriginal"); + Exiv2::ExifData::iterator md = exifData.findKey(key); + if (md == exifData.end()) { + std::cerr << "Metadatum with key `" << key << "' " + << "not found in the file " << path << "\n"; + return 1; + } + std::string v = md->toString(); + if (v.length() == 0 || v[0] == ' ') { + std::cerr << "Image file creation timestamp not set in the file " + << path << "\n"; + return 1; + } + // Assemble the new filename from the timestamp + struct tm tm; + if (str2Tm(v, &tm) != 0) { + std::cerr << "Failed to parse timestamp `" << v + << "' in the file " << path << "\n"; + return 1; + } + const size_t max = 1024; + char basename[max]; + memset(basename, 0x0, max); + if (strftime(basename, max, Params::instance().format_.c_str(), &tm) == 0) { + std::cerr << "Filename format yields empty filename for the file " + << path << "\n"; + return 1; + } + std::string newPath + = Util::dirname(path) + EXV_SEPERATOR_STR + basename + Util::suffix(path); + if ( Util::dirname(newPath) == Util::dirname(path) + && Util::basename(newPath) == Util::basename(path)) { + if (Params::instance().verbose_) { + std::cout << "This file already has the correct name" << std::endl; + } + return 0; + } + + bool go = true; + int seq = 1; + std::string s; + Params::FileExistsPolicy fileExistsPolicy + = Params::instance().fileExistsPolicy_; + while (go) { + if (Exiv2::fileExists(newPath)) { + switch (fileExistsPolicy) { + case Params::overwritePolicy: + go = false; + break; + case Params::renamePolicy: + newPath = Util::dirname(path) + + EXV_SEPERATOR_STR + basename + + "_" + Exiv2::toString(seq++) + + Util::suffix(path); + break; + case Params::askPolicy: + std::cout << Params::instance().progname() + << ": File `" << newPath + << "' exists. [O]verwrite, [r]ename or [s]kip? "; + std::cin >> s; + switch (s[0]) { + case 'o': + case 'O': + go = false; + break; + case 'r': + case 'R': + fileExistsPolicy = Params::renamePolicy; + newPath = Util::dirname(path) + + EXV_SEPERATOR_STR + basename + + "_" + Exiv2::toString(seq++) + + Util::suffix(path); + break; + default: // skip + return 0; + break; + } + } + } + else { + go = false; + } + } + + if (Params::instance().verbose_) { + std::cout << "Renaming file to " << newPath << std::endl; + } + + // Workaround for MinGW rename which does not overwrite existing files + remove(newPath.c_str()); + if (::rename(path.c_str(), newPath.c_str()) == -1) { + std::cerr << Params::instance().progname() + << ": Failed to rename " + << path << " to " << newPath << ": " + << Exiv2::strError() << "\n"; + return 1; + } + return 0; + } + catch(const Exiv2::AnyError& e) + { + std::cerr << "Exiv2 exception in rename action for file " << path + << ":\n" << e << "\n"; + return 1; + } // Rename::run + + Rename::AutoPtr Rename::clone() const + { + return AutoPtr(clone_()); + } + + Rename* Rename::clone_() const + { + return new Rename(*this); + } + + int Erase::run(const std::string& path) + try { + path_ = path; + + 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(); + // Thumbnail must be before Exif + int rc = 0; + if (Params::instance().target_ & Params::ctThumb) { + rc = eraseThumbnail(image.get()); + } + if (0 == rc && Params::instance().target_ & Params::ctExif) { + rc = eraseExifData(image.get()); + } + if (0 == rc && Params::instance().target_ & Params::ctIptc) { + rc = eraseIptcData(image.get()); + } + if (0 == rc && Params::instance().target_ & Params::ctComment) { + rc = eraseComment(image.get()); + } + if (0 == rc) { + image->writeMetadata(); + } + + return rc; + } + catch(const Exiv2::AnyError& e) + { + std::cerr << "Exiv2 exception in erase action for file " << path + << ":\n" << e << "\n"; + return 1; + } // Erase::run + + int Erase::eraseThumbnail(Exiv2::Image* image) const + { + Exiv2::ExifData &exifData = image->exifData(); + std::string thumbExt = exifData.thumbnailExtension(); + if (thumbExt.empty()) { + return 0; + } + long delta = exifData.eraseThumbnail(); + if (Params::instance().verbose_) { + std::cout << "Erasing " << delta + << " Bytes of thumbnail data" << std::endl; + } + return 0; + } + + int Erase::eraseExifData(Exiv2::Image* image) const + { + if (Params::instance().verbose_ && image->exifData().count() > 0) { + std::cout << "Erasing Exif data from the file" << std::endl; + } + image->clearExifData(); + return 0; + } + + 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; + } + image->clearIptcData(); + return 0; + } + + 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; + } + image->clearComment(); + return 0; + } + + Erase::AutoPtr Erase::clone() const + { + return AutoPtr(clone_()); + } + + Erase* Erase::clone_() const + { + return new Erase(*this); + } + + int Extract::run(const std::string& path) + try { + path_ = path; + int rc = 0; + 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; + } + rc = metacopy(path_, exvPath, false); + } + return rc; + } + catch(const Exiv2::AnyError& e) + { + std::cerr << "Exiv2 exception in extract action for file " << path + << ":\n" << e << "\n"; + return 1; + } // Extract::run + + int Extract::writeThumbnail() 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(); + Exiv2::ExifData &exifData = image->exifData(); + if (exifData.empty()) { + std::cerr << path_ + << ": 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; + if (thumbExt.empty()) { + std::cerr << path_ << ": Image does not contain an Exif thumbnail\n"; + } + else { + 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; + } + rc = exifData.writeThumbnail(thumb); + if (rc) { + std::cerr << thumb << ": Exif data doesn't contain a thumbnail\n"; + } + } + return rc; + } // Extract::writeThumbnail + + Extract::AutoPtr Extract::clone() const + { + return AutoPtr(clone_()); + } + + Extract* Extract::clone_() const + { + return new Extract(*this); + } + + int Insert::run(const std::string& path) + try { + if (!Exiv2::fileExists(path, true)) { + std::cerr << path + << ": Failed to open the file\n"; + return -1; + } + int rc = 0; + if (Params::instance().target_ & Params::ctThumb) { + rc = insertThumbnail(path); + } + 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); + std::string exvPath = directory + EXV_SEPERATOR_STR + + Util::basename(path, true) + ".exv"; + rc = metacopy(exvPath, path, true); + } + return rc; + } + catch(const Exiv2::AnyError& e) + { + std::cerr << "Exiv2 exception in insert action for file " << path + << ":\n" << e << "\n"; + return 1; + } // Insert::run + + 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"; + if (!Exiv2::fileExists(thumbPath, true)) { + std::cerr << thumbPath + << ": Failed to open the file\n"; + return -1; + } + 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::ExifData &exifData = image->exifData(); + exifData.setJpegThumbnail(thumbPath); + image->writeMetadata(); + + return 0; + } // Insert::insertThumbnail + + Insert::AutoPtr Insert::clone() const + { + return AutoPtr(clone_()); + } + + Insert* Insert::clone_() const + { + return new Insert(*this); + } + + int Modify::run(const std::string& path) + try { + if (!Exiv2::fileExists(path, true)) { + std::cerr << path + << ": Failed to open the file\n"; + return -1; + } + image_ = Exiv2::ImageFactory::open(path); + assert(image_.get() != 0); + image_->readMetadata(); + + // loop through command table and apply each command + ModifyCmds& modifyCmds = Params::instance().modifyCmds_; + ModifyCmds::const_iterator i = modifyCmds.begin(); + ModifyCmds::const_iterator end = modifyCmds.end(); + for (; i != end; ++i) { + switch (i->cmdId_) { + case add: + addMetadatum(*i); + break; + case set: + setMetadatum(*i); + break; + case del: + delMetadatum(*i); + break; + default: + // Todo: complain + break; + } + } + + // Save both exif and iptc metadata + image_->writeMetadata(); + + return 0; + } + catch(const Exiv2::AnyError& e) + { + std::cerr << "Exiv2 exception in modify action for file " << path + << ":\n" << e << "\n"; + return 1; + } // Modify::run + + void Modify::addMetadatum(const ModifyCmd& modifyCmd) + { + if (Params::instance().verbose_) { + std::cout << "Add " << modifyCmd.key_ << " \"" + << modifyCmd.value_ << "\" (" + << Exiv2::TypeInfo::typeName(modifyCmd.typeId_) + << ")" << std::endl; + } + Exiv2::Value::AutoPtr value = Exiv2::Value::create(modifyCmd.typeId_); + value->read(modifyCmd.value_); + if (modifyCmd.metadataId_ == exif) { + image_->exifData().add(Exiv2::ExifKey(modifyCmd.key_), value.get()); + } + if (modifyCmd.metadataId_ == iptc) { + image_->iptcData().add(Exiv2::IptcKey(modifyCmd.key_), value.get()); + } + } + + void Modify::setMetadatum(const ModifyCmd& modifyCmd) + { + if (Params::instance().verbose_) { + std::cout << "Set " << modifyCmd.key_ << " \"" + << modifyCmd.value_ << "\" (" + << Exiv2::TypeInfo::typeName(modifyCmd.typeId_) + << ")" << std::endl; + } + + Exiv2::ExifData &exifData = image_->exifData(); + Exiv2::IptcData &iptcData = image_->iptcData(); + Exiv2::Metadatum* metadatum = 0; + if (modifyCmd.metadataId_ == exif) { + metadatum = &exifData[modifyCmd.key_]; + } + if (modifyCmd.metadataId_ == iptc) { + metadatum = &iptcData[modifyCmd.key_]; + } + assert(metadatum); + Exiv2::Value::AutoPtr value = metadatum->getValue(); + // If a type was explicitly requested, use it; else + // use the current type of the metadatum, if any; + // or the default type + if (modifyCmd.explicitType_ || value.get() == 0) { + value = Exiv2::Value::create(modifyCmd.typeId_); + } + value->read(modifyCmd.value_); + metadatum->setValue(value.get()); + } + + void Modify::delMetadatum(const ModifyCmd& modifyCmd) + { + if (Params::instance().verbose_) { + std::cout << "Del " << modifyCmd.key_ << std::endl; + } + + Exiv2::ExifData &exifData = image_->exifData(); + Exiv2::IptcData &iptcData = image_->iptcData(); + if (modifyCmd.metadataId_ == exif) { + Exiv2::ExifData::iterator pos = + exifData.findKey(Exiv2::ExifKey(modifyCmd.key_)); + if (pos != exifData.end()) exifData.erase(pos); + } + if (modifyCmd.metadataId_ == iptc) { + Exiv2::IptcData::iterator pos = + iptcData.findKey(Exiv2::IptcKey(modifyCmd.key_)); + if (pos != iptcData.end()) iptcData.erase(pos); + } + } + + Modify::AutoPtr Modify::clone() const + { + return AutoPtr(clone_()); + } + + Modify* Modify::clone_() const + { + return new Modify(*this); + } + + int Adjust::run(const std::string& path) + try { + adjustment_ = Params::instance().adjustment_; + + 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::ExifData &exifData = image->exifData(); + if (exifData.empty()) { + std::cerr << path + << ": No Exif data found in the file\n"; + return -3; + } + int rc = adjustDateTime(exifData, "Exif.Image.DateTime", path); + rc += adjustDateTime(exifData, "Exif.Photo.DateTimeOriginal", path); + rc += adjustDateTime(exifData, "Exif.Photo.DateTimeDigitized", path); + if (rc) return 1; + image->writeMetadata(); + return rc; + } + catch(const Exiv2::AnyError& e) + { + std::cerr << "Exiv2 exception in adjust action for file " << path + << ":\n" << e << "\n"; + return 1; + } // Adjust::run + + Adjust::AutoPtr Adjust::clone() const + { + return AutoPtr(clone_()); + } + + Adjust* Adjust::clone_() const + { + return new Adjust(*this); + } + + int Adjust::adjustDateTime(Exiv2::ExifData& exifData, + const std::string& key, + const std::string& path) const + { + Exiv2::ExifKey ek(key); + Exiv2::ExifData::iterator md = exifData.findKey(ek); + if (md == exifData.end()) { + // Key not found. That's ok, we do nothing. + return 0; + } + std::string timeStr = md->toString(); + if (timeStr == "" || timeStr[0] == ' ') { + std::cerr << path << ": Timestamp of metadatum with key `" + << ek << "' not set\n"; + return 1; + } + time_t time = str2Time(timeStr); + if (time == (time_t)-1) { + std::cerr << path << ": Failed to parse or convert timestamp `" + << timeStr << "'\n"; + return 1; + } + if (Params::instance().verbose_) { + std::cout << "Adjusting `" << ek << "' by" + << (adjustment_ < 0 ? " " : " +") + << adjustment_ << " s to "; + } + time += adjustment_; + timeStr = time2Str(time); + if (Params::instance().verbose_) { + std::cout << timeStr << std::endl; + } + md->setValue(timeStr); + return 0; + } // Adjust::adjustDateTime + +} // namespace Action + +// ***************************************************************************** +// local definitions +namespace { + + int str2Tm(const std::string& timeStr, struct tm* tm) + { + if (timeStr.length() == 0 || timeStr[0] == ' ') return 1; + if (timeStr.length() < 19) return 2; + if ( timeStr[4] != ':' || timeStr[7] != ':' || timeStr[10] != ' ' + || timeStr[13] != ':' || timeStr[16] != ':') return 3; + if (0 == tm) return 4; + memset(tm, 0x0, sizeof(struct tm)); + + long tmp; + if (!Util::strtol(timeStr.substr(0,4).c_str(), tmp)) return 5; + tm->tm_year = tmp - 1900; + if (!Util::strtol(timeStr.substr(5,2).c_str(), tmp)) return 6; + tm->tm_mon = tmp - 1; + if (!Util::strtol(timeStr.substr(8,2).c_str(), tmp)) return 7; + tm->tm_mday = tmp; + if (!Util::strtol(timeStr.substr(11,2).c_str(), tmp)) return 8; + tm->tm_hour = tmp; + if (!Util::strtol(timeStr.substr(14,2).c_str(), tmp)) return 9; + tm->tm_min = tmp; + if (!Util::strtol(timeStr.substr(17,2).c_str(), tmp)) return 10; + tm->tm_sec = tmp; + + return 0; + } // str2Tm + + time_t str2Time(const std::string& timeStr) + { + struct tm tm; + if (str2Tm(timeStr, &tm) != 0) return (time_t)-1; + time_t t = timegm(&tm); + return t; + } + + std::string time2Str(time_t time) + { + struct tm* tm = gmtime(&time); + if (0 == tm) return ""; + + std::ostringstream os; + os << std::setfill('0') + << tm->tm_year + 1900 << ":" + << std::setw(2) << tm->tm_mon + 1 << ":" + << std::setw(2) << tm->tm_mday << " " + << std::setw(2) << tm->tm_hour << ":" + << std::setw(2) << tm->tm_min << ":" + << std::setw(2) << tm->tm_sec; + + return os.str(); + } // time2Str + + int metacopy(const std::string& source, + const std::string& target, + bool preserve) + { + if (!Exiv2::fileExists(source, true)) { + std::cerr << source + << ": Failed to open the file\n"; + return -1; + } + Exiv2::Image::AutoPtr sourceImage = Exiv2::ImageFactory::open(source); + assert(sourceImage.get() != 0); + sourceImage->readMetadata(); + + Exiv2::Image::AutoPtr targetImage; + if (Exiv2::fileExists(target)) { + targetImage = Exiv2::ImageFactory::open(target); + assert(targetImage.get() != 0); + if (preserve) targetImage->readMetadata(); + } + else { + targetImage + = Exiv2::ImageFactory::create(Exiv2::Image::exv, target); + assert(targetImage.get() != 0); + } + if ( Params::instance().target_ & Params::ctExif + && !sourceImage->exifData().empty()) { + if (Params::instance().verbose_) { + std::cout << "Writing Exif data from " << source + << " to " << target << std::endl; + } + targetImage->setExifData(sourceImage->exifData()); + } + if ( Params::instance().target_ & Params::ctIptc + && !sourceImage->iptcData().empty()) { + if (Params::instance().verbose_) { + std::cout << "Writing Iptc data from " << source + << " to " << target << std::endl; + } + targetImage->setIptcData(sourceImage->iptcData()); + } + if ( Params::instance().target_ & Params::ctComment + && !sourceImage->comment().empty()) { + if (Params::instance().verbose_) { + std::cout << "Writing Jpeg comment from " << source + << " to " << target << std::endl; + } + targetImage->setComment(sourceImage->comment()); + } + try { + targetImage->writeMetadata(); + } + catch (const Exiv2::AnyError& e) { + std::cerr << target << + ": Could not write metadata to file: " << e << "\n"; + } + + return 0; + } // metacopy +} diff --git a/src/plugins/exiv2/actions.hpp b/src/plugins/exiv2/actions.hpp @@ -0,0 +1,328 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file actions.hpp + @brief Implements base class Task, TaskFactory and the various supported + actions (derived from Task). + @version $Rev: 575 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 11-Dec-03, ahu: created + */ +#ifndef ACTIONS_HPP_ +#define ACTIONS_HPP_ + +// ***************************************************************************** +// included header files + +// + standard includes +#include <string> +#include <map> + +#include "exiv2.hpp" +#include "image.hpp" +#include "exif.hpp" +#include "iptc.hpp" + +// ***************************************************************************** +// class declarations + +namespace Exiv2 { + class ExifData; + class Image; +} + +// ***************************************************************************** +// namespace extensions +/*! + @brief Contains all action classes (task subclasses). + */ +namespace Action { + + //! Enumerates all tasks + enum TaskType { none, adjust, print, rename, erase, extract, insert, modify }; + +// ***************************************************************************** +// class definitions + + /*! + @brief Abstract base class for all concrete actions. + + Task provides a simple interface that actions must implement and a few + commonly used helpers. + */ + class Task { + public: + //! Shortcut for an auto pointer. + typedef std::auto_ptr<Task> AutoPtr; + //! Virtual destructor. + virtual ~Task() {} + //! Virtual copy construction. + AutoPtr clone() const; + /*! + @brief Application interface to perform a task. + + @param path Path of the file to process. + @return 0 if successful. + */ + virtual int run(const std::string& path) =0; + + private: + //! Internal virtual copy constructor. + virtual Task* clone_() const =0; + + }; // class Task + + /*! + @brief Task factory. + + Creates an instance of the task of the requested type. The factory is + implemented as a singleton, which can be accessed only through the static + member function instance(). + */ + class TaskFactory { + public: + /*! + @brief Get access to the task factory. + + Clients access the task factory exclusively through + this method. + */ + static TaskFactory& instance(); + + /*! + @brief Create a task. + + @param type Identifies the type of task to create. + @return An auto pointer that owns a task of the requested type. If + the task type is not supported, the pointer is 0. + @remark The caller of the function should check the content of the + returned auto pointer and take appropriate action (e.g., throw + an exception) if it is 0. + */ + Task::AutoPtr create(TaskType type); + + /*! + @brief Register a task prototype together with its type. + + The task factory creates new tasks of a given type by cloning its + associated prototype. Additional tasks can be registered. If called + for a type which already exists in the list, the corresponding + prototype is replaced. + + @param type Task type. + @param task Pointer to the prototype. Ownership is transfered to the + task factory. That's what the auto pointer indicates. + */ + void registerTask(TaskType type, Task::AutoPtr task); + + private: + //! Prevent construction other than through instance(). + TaskFactory(); + //! Prevent copy construction: not implemented. + TaskFactory(const TaskFactory& rhs); + + //! Pointer to the one and only instance of this class. + static TaskFactory* instance_; + //! Type used to store Task prototype classes + typedef std::map<TaskType, Task*> Registry; + //! List of task types and corresponding prototypes. + Registry registry_; + + }; // class TaskFactory + + //! %Print the Exif (or other metadata) of a file to stdout + class Print : public Task { + public: + virtual ~Print() {} + virtual int run(const std::string& path); + typedef std::auto_ptr<Print> AutoPtr; + AutoPtr clone() const; + + //! Print the Jpeg comment + int printComment(); + //! Print uninterpreted Iptc information + int printIptc(); + //! Print Exif summary information + int printSummary(); + //! Print the interpreted value for each Exif tag + int printInterpreted(); + //! Print uninterpreted Exif information + int printValues(); + //! Print Exif information in hexdump format + int printHexdump(); + /*! + @brief Print one summary line with a label (if provided) and requested + data. A line break is printed only if a label is provided. + @return 1 if a line was written, 0 if the key was not found. + */ + int printTag(const Exiv2::ExifData& exifData, + const std::string& key, + const std::string& label ="") const; + + private: + virtual Print* clone_() const; + + std::string path_; + int align_; // for the alignment of the summary output + }; // class Print + + /*! + @brief %Rename a file to its metadate creation timestamp, + in the specified format. + */ + class Rename : public Task { + public: + virtual ~Rename() {} + virtual int run(const std::string& path); + typedef std::auto_ptr<Rename> AutoPtr; + AutoPtr clone() const; + + private: + virtual Rename* clone_() const; + }; // class Rename + + //! %Adjust the Exif (or other metadata) timestamps + class Adjust : public Task { + public: + virtual ~Adjust() {} + virtual int run(const std::string& path); + typedef std::auto_ptr<Adjust> AutoPtr; + AutoPtr clone() const; + + private: + virtual Adjust* clone_() const; + int adjustDateTime(Exiv2::ExifData& exifData, + const std::string& key, + const std::string& path) const; + + long adjustment_; + }; // class Adjust + + /*! + @brief %Erase the entire exif data or only the thumbnail section. + */ + class Erase : public Task { + public: + virtual ~Erase() {} + virtual int run(const std::string& path); + typedef std::auto_ptr<Erase> AutoPtr; + AutoPtr clone() const; + + /*! + @brief Delete the thumbnail image, incl IFD1 metadata from the file. + */ + int eraseThumbnail(Exiv2::Image* image) const; + /*! + @brief Erase the complete Exif data block from the file. + */ + int eraseExifData(Exiv2::Image* image) const; + /*! + @brief Erase all Iptc data from the file. + */ + int eraseIptcData(Exiv2::Image* image) const; + /*! + @brief Erase Jpeg comment from the file. + */ + int eraseComment(Exiv2::Image* image) const; + + private: + virtual Erase* clone_() const; + std::string path_; + + }; // class Erase + + /*! + @brief %Extract the entire exif data or only the thumbnail section. + */ + class Extract : public Task { + public: + virtual ~Extract() {} + virtual int run(const std::string& path); + typedef std::auto_ptr<Extract> AutoPtr; + AutoPtr clone() const; + + /*! + @brief Write the thumbnail image to a file. The filename is composed by + removing the suffix from the image filename and appending + "-thumb" and the appropriate suffix (".jpg" or ".tif"), depending + on the format of the Exif thumbnail image. + */ + int writeThumbnail() const; + + private: + virtual Extract* clone_() const; + std::string path_; + + }; // class Extract + + /*! + @brief %Insert the Exif data from corresponding *.exv files. + */ + class Insert : public Task { + public: + virtual ~Insert() {} + virtual int run(const std::string& path); + typedef std::auto_ptr<Insert> AutoPtr; + AutoPtr clone() const; + + /*! + @brief Insert a Jpeg thumbnail image from a file into file \em path. + The filename of the thumbanail is expected to be the image + filename (\em path) minus its suffix plus "-thumb.jpg". + */ + int insertThumbnail(const std::string& path) const; + + private: + virtual Insert* clone_() const; + + }; // class Insert + + /*! + @brief %Modify the Exif data according to the commands in the + modification table. + */ + class Modify : public Task { + public: + virtual ~Modify() {} + virtual int run(const std::string& path); + typedef std::auto_ptr<Modify> AutoPtr; + AutoPtr clone() const; + Modify() {} + + private: + virtual Modify* clone_() const; + //! Copy contructor needed because of AutoPtr memeber + Modify(const Modify& src) {} + + //! Add a metadatum according to \em modifyCmd + void addMetadatum(const ModifyCmd& modifyCmd); + //! Set a metadatum according to \em modifyCmd + void setMetadatum(const ModifyCmd& modifyCmd); + //! Delete a metadatum according to \em modifyCmd + void delMetadatum(const ModifyCmd& modifyCmd); + + Exiv2::Image::AutoPtr image_; //!< Image to modify + }; // class Modify + +} // namespace Action + +#endif // #ifndef ACTIONS_HPP_ diff --git a/src/plugins/exiv2/addmoddel.cpp b/src/plugins/exiv2/addmoddel.cpp @@ -0,0 +1,109 @@ +// ***************************************************************** -*- C++ -*- +// addmoddel.cpp, $Rev: 560 $ +// Sample program showing how to add, modify and delete Exif metadata. + +#include "image.hpp" +#include "exif.hpp" +#include <iostream> +#include <iomanip> +#include <cassert> + +int main(int argc, char* const argv[]) +try { + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " file\n"; + return 1; + } + std::string file(argv[1]); + + // Container for exif metadata. This is an example of creating + // exif metadata from scratch. If you want to add, modify, delete + // metadata that exists in an image, start with ImageFactory::open + Exiv2::ExifData exifData; + + // ************************************************************************* + // Add to the Exif data + + // This is the quickest way to add (simple) Exif data. If a metadatum for + // a given key already exists, its value is overwritten. Otherwise a new + // tag is added. + exifData["Exif.Image.Model"] = "Test 1"; // AsciiValue + exifData["Exif.Image.SamplesPerPixel"] = uint16_t(162); // UShortValue + exifData["Exif.Image.XResolution"] = int32_t(-2); // LongValue + exifData["Exif.Image.YResolution"] = Exiv2::Rational(-2, 3); // RationalValue + std::cout << "Added a few tags the quick way.\n"; + + // Create a ASCII string value (note the use of create) + Exiv2::Value::AutoPtr v = Exiv2::Value::create(Exiv2::asciiString); + // Set the value to a string + v->read("1999:12:31 23:59:59"); + // Add the value together with its key to the Exif data container + Exiv2::ExifKey key("Exif.Photo.DateTimeOriginal"); + exifData.add(key, v.get()); + std::cout << "Added key \"" << key << "\", value \"" << *v << "\"\n"; + + // Now create a more interesting value (without using the create method) + Exiv2::URationalValue::AutoPtr rv(new Exiv2::URationalValue); + // Set two rational components from a string + rv->read("1/2 1/3"); + // Add more elements through the extended interface of rational value + rv->value_.push_back(std::make_pair(2,3)); + rv->value_.push_back(std::make_pair(3,4)); + // Add the key and value pair to the Exif data + key = Exiv2::ExifKey("Exif.Image.PrimaryChromaticities"); + exifData.add(key, rv.get()); + std::cout << "Added key \"" << key << "\", value \"" << *rv << "\"\n"; + + // ************************************************************************* + // Modify Exif data + + // Since we know that the metadatum exists (or we don't mind creating a new + // tag if it doesn't), we can simply do this: + Exiv2::Exifdatum& tag = exifData["Exif.Photo.DateTimeOriginal"]; + std::string date = tag.toString(); + date.replace(0, 4, "2000"); + tag.setValue(date); + std::cout << "Modified key \"" << key + << "\", new value \"" << tag.value() << "\"\n"; + + // Alternatively, we can use findKey() + key = Exiv2::ExifKey("Exif.Image.PrimaryChromaticities"); + Exiv2::ExifData::iterator pos = exifData.findKey(key); + if (pos == exifData.end()) throw Exiv2::Error(1, "Key not found"); + // Get a pointer to a copy of the value + v = pos->getValue(); + // Downcast the Value pointer to its actual type + Exiv2::URationalValue* prv = dynamic_cast<Exiv2::URationalValue*>(v.release()); + if (prv == 0) throw Exiv2::Error(1, "Downcast failed"); + rv = Exiv2::URationalValue::AutoPtr(prv); + // Modify the value directly through the interface of URationalValue + rv->value_[2] = std::make_pair(88,77); + // Copy the modified value back to the metadatum + pos->setValue(rv.get()); + std::cout << "Modified key \"" << key + << "\", new value \"" << pos->value() << "\"\n"; + + // ************************************************************************* + // Delete metadata from the Exif data container + + // Delete the metadatum at iterator position pos + key = Exiv2::ExifKey("Exif.Image.PrimaryChromaticities"); + pos = exifData.findKey(key); + if (pos == exifData.end()) throw Exiv2::Error(1, "Key not found"); + exifData.erase(pos); + std::cout << "Deleted key \"" << key << "\"\n"; + + // ************************************************************************* + // Finally, write the remaining Exif data to the image file + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(file); + assert(image.get() != 0); + + image->setExifData(exifData); + image->writeMetadata(); + + return 0; +} +catch (Exiv2::AnyError& e) { + std::cout << "Caught Exiv2 exception '" << e << "'\n"; + return -1; +} diff --git a/src/plugins/exiv2/basicio.cpp b/src/plugins/exiv2/basicio.cpp @@ -0,0 +1,500 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: basicio.cpp + Version: $Rev: 566 $ + Author(s): Brad Schick (brad) <brad@robotbattle.com> + History: 04-Dec-04, brad: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: basicio.cpp 566 2005-04-26 15:27:41Z ahuggel $"); + +// ***************************************************************************** +// included header files +#ifdef _MSC_VER +# include "exv_msvc.h" +#else +# include "exv_conf.h" +#endif + +#include "basicio.hpp" +#include "futils.hpp" +#include "types.hpp" +#include "error.hpp" + +// + standard includes +#include <string> +#include <cassert> +#include <cstdio> // for remove() +#include <sys/types.h> // for stat() +#include <sys/stat.h> // for stat() +#ifdef EXV_HAVE_PROCESS_H +# include <process.h> +#endif +#ifdef EXV_HAVE_UNISTD_H +# include <unistd.h> // for getpid, stat +#endif + +#if defined WIN32 && !defined __CYGWIN__ +# include <io.h> +#endif + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + FileIo::FileIo(const std::string& path) : + path_(path), fp_(0), opMode_(opSeek) + { + } + + FileIo::~FileIo() + { + close(); + } + + BasicIo::AutoPtr FileIo::temporary() const + { + BasicIo::AutoPtr basicIo; + + struct stat buf; + int ret = stat(path_.c_str(), &buf); + + // If file is > 1MB then use a file, otherwise use memory buffer + if (ret != 0 || buf.st_size > 1048576) { + pid_t pid = getpid(); + std::string tmpname = path_ + toString(pid); + std::auto_ptr<FileIo> fileIo(new FileIo(tmpname)); + if (fileIo->open("w+b") != 0) { + throw Error(10, path_, "w+b", strError()); + } + basicIo = fileIo; + } + else { + basicIo.reset(new MemIo); + } + + return basicIo; + } + + int FileIo::switchMode(OpMode opMode) + { + assert(fp_ != 0); + if (opMode_ == opMode) return 0; + OpMode oldOpMode = opMode_; + opMode_ = opMode; + + bool reopen = true; + std::string mode = "r+b"; + + switch(opMode) { + case opRead: + // Flush if current mode allows reading, else reopen (in mode "r+b" + // as in this case we know that we can write to the file) + if ( openMode_[0] == 'r' + || openMode_.substr(0, 2) == "w+" + || openMode_.substr(0, 2) == "a+") reopen = false; + break; + case opWrite: + // Flush if current mode allows writing, else reopen + if ( openMode_.substr(0, 2) == "r+" + || openMode_[0] == 'w' + || openMode_[0] == 'a') reopen = false; + break; + case opSeek: + reopen = false; + break; + } + + if (!reopen) { + // Don't do anything when switching _from_ opSeek mode; we + // flush when switching _to_ opSeek. + if (oldOpMode == opSeek) return 0; + + // Flush. On msvcrt fflush does not do the job + fseek(fp_, 0, SEEK_CUR); + return 0; + } + + // Reopen the file + long offset = ftell(fp_); + if (offset == -1) return -1; + if (open(mode) != 0) return 1; + return fseek(fp_, offset, SEEK_SET); + } + + long FileIo::write(const byte* data, long wcount) + { + assert(fp_ != 0); + if (switchMode(opWrite) != 0) return 0; + return (long)fwrite(data, 1, wcount, fp_); + } + + long FileIo::write(BasicIo& src) + { + assert(fp_ != 0); + if (static_cast<BasicIo*>(this) == &src) return 0; + if (!src.isopen()) return 0; + if (switchMode(opWrite) != 0) return 0; + + byte buf[4096]; + long readCount = 0; + long writeCount = 0; + long writeTotal = 0; + while ((readCount = src.read(buf, sizeof(buf)))) { + writeTotal += writeCount = (long)fwrite(buf, 1, readCount, fp_); + if (writeCount != readCount) { + // try to reset back to where write stopped + src.seek(writeCount-readCount, BasicIo::cur); + break; + } + } + + return writeTotal; + } + + void FileIo::transfer(BasicIo& src) + { + const bool wasOpen = (fp_ != 0); + const std::string lastMode(openMode_); + + FileIo *fileIo = dynamic_cast<FileIo*>(&src); + if (fileIo) { + // Optimization if this is another instance of FileIo + close(); + fileIo->close(); + // MSVCRT rename that does not overwrite existing files + if (remove(path_.c_str()) != 0) { + throw Error(2, path_, strError(), "::remove"); + } + if (rename(fileIo->path_.c_str(), path_.c_str()) == -1) { + throw Error(17, fileIo->path_, path_, strError()); + } + remove(fileIo->path_.c_str()); + } + else{ + // Generic handling, reopen both to reset to start + if (open("w+b") != 0) { + throw Error(10, path_, "w+b", strError()); + } + if (src.open() != 0) { + throw Error(9, src.path(), strError()); + } + write(src); + src.close(); + } + + if (wasOpen) { + if (open(lastMode) != 0) { + throw Error(10, path_, lastMode, strError()); + } + } + else close(); + + if (error() || src.error()) throw Error(18, path_, strError()); + } + + int FileIo::putb(byte data) + { + assert(fp_ != 0); + if (switchMode(opWrite) != 0) return EOF; + return putc(data, fp_); + } + + int FileIo::seek(long offset, Position pos) + { + assert(fp_ != 0); + int fileSeek; + if (pos == BasicIo::cur) { + fileSeek = SEEK_CUR; + } + else if (pos == BasicIo::beg) { + fileSeek = SEEK_SET; + } + else { + assert(pos == BasicIo::end); + fileSeek = SEEK_END; + } + + if (switchMode(opSeek) != 0) return 1; + return fseek(fp_, offset, fileSeek); + } + + long FileIo::tell() const + { + assert(fp_ != 0); + return ftell(fp_); + } + + + long FileIo::size() const + { + if (fp_ != 0) { + fflush(fp_); +#if defined WIN32 && !defined __CYGWIN__ + // This is required on msvcrt before stat after writing to a file + _commit(_fileno(fp_)); +#endif + } + + struct stat buf; + int ret = stat(path_.c_str(), &buf); + + if (ret != 0) return -1; + return buf.st_size; + } + + int FileIo::open() + { + // Default open is in read-only binary mode + return open("rb"); + } + + int FileIo::open(const std::string& mode) + { + if (fp_ != 0) { + fclose(fp_); + } + + openMode_ = mode; + opMode_ = opSeek; + fp_ = fopen(path_.c_str(), mode.c_str()); + if (!fp_) return 1; + return 0; + } + + bool FileIo::isopen() const + { + return fp_ != 0; + } + + int FileIo::close() + { + if (fp_ != 0) { + fclose(fp_); + fp_= 0; + } + return 0; + } + + DataBuf FileIo::read(long rcount) + { + assert(fp_ != 0); + DataBuf buf(rcount); + long readCount = read(buf.pData_, buf.size_); + buf.size_ = readCount; + return buf; + } + + long FileIo::read(byte* buf, long rcount) + { + assert(fp_ != 0); + if (switchMode(opRead) != 0) return 0; + return (long)fread(buf, 1, rcount, fp_); + } + + int FileIo::getb() + { + assert(fp_ != 0); + if (switchMode(opRead) != 0) return EOF; + return getc(fp_); + } + + int FileIo::error() const + { + return fp_ != 0 ? ferror(fp_) : 0; + } + + bool FileIo::eof() const + { + assert(fp_ != 0); + return feof(fp_) != 0; + } + + std::string FileIo::path() const + { + return path_; + } + + MemIo::MemIo(const byte* data, long size) + { + // If copying data is too slow it might be worth + // creating a readonly MemIo variant + data_.reserve(size); + data_.assign(data, data+size); + idx_ = 0; + } + + BasicIo::AutoPtr MemIo::temporary() const + { + return BasicIo::AutoPtr(new MemIo); + } + + void MemIo::checkSize(long wcount) + { + ByteVector::size_type need = wcount + idx_; + if (need > data_.size()) { + data_.resize(need); + } + } + + long MemIo::write(const byte* data, long wcount) + { + checkSize(wcount); + // According to Josuttis 6.2.3 this is safe + memcpy(&data_[idx_], data, wcount); + idx_ += wcount; + return wcount; + } + + void MemIo::transfer(BasicIo& src) + { + MemIo *memIo = dynamic_cast<MemIo*>(&src); + if (memIo) { + // Optimization if this is another instance of MemIo + data_.swap(memIo->data_); + idx_ = 0; + } + else{ + // Generic reopen to reset position to start + data_.clear(); + idx_ = 0; + if (src.open() != 0) { + throw Error(9, src.path(), strError()); + } + write(src); + src.close(); + } + if (error() || src.error()) throw Error(19, strError()); + } + + long MemIo::write(BasicIo& src) + { + if (static_cast<BasicIo*>(this)==&src) return 0; + if (!src.isopen()) return 0; + + byte buf[4096]; + long readCount = 0; + long writeTotal = 0; + while ((readCount = src.read(buf, sizeof(buf)))) { + write(buf, readCount); + writeTotal += readCount; + } + + return writeTotal; + } + + int MemIo::putb(byte data) + { + checkSize(1); + data_[idx_++] = data; + return data; + } + + int MemIo::seek(long offset, Position pos) + { + ByteVector::size_type newIdx; + + if (pos == BasicIo::cur ) { + newIdx = idx_ + offset; + } + else if (pos == BasicIo::beg) { + newIdx = offset; + } + else { + assert(pos == BasicIo::end); + newIdx = data_.size() + offset; + } + + if (newIdx < 0 || newIdx > data_.size()) return 1; + idx_ = newIdx; + return 0; + } + + long MemIo::tell() const + { + return (long)idx_; + } + + long MemIo::size() const + { + return (long)data_.size(); + } + + int MemIo::open() + { + idx_ = 0; + return 0; + } + + bool MemIo::isopen() const + { + return true; + } + + int MemIo::close() + { + return 0; + } + + DataBuf MemIo::read(long rcount) + { + DataBuf buf(rcount); + long readCount = read(buf.pData_, buf.size_); + buf.size_ = readCount; + return buf; + } + + long MemIo::read(byte* buf, long rcount) + { + long avail = (long)(data_.size() - idx_); + long allow = std::min(rcount, avail); + + // According to Josuttis 6.2.3 this is safe + memcpy(buf, &data_[idx_], allow); + idx_ += allow; + return allow; + } + + int MemIo::getb() + { + if (idx_ == data_.size()) + return EOF; + return data_[idx_++]; + } + + int MemIo::error() const + { + return 0; + } + + bool MemIo::eof() const + { + return idx_ == data_.size(); + } + + std::string MemIo::path() const + { + return "MemIo"; + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/basicio.hpp b/src/plugins/exiv2/basicio.hpp @@ -0,0 +1,642 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file basicio.hpp + @brief Simple binary IO abstraction + @version $Rev: 565 $ + @author Brad Schick (brad) + <a href="mailto:brad@robotbattle.com">brad@robotbattle.com</a> + @date 04-Dec-04, brad: created + */ +#ifndef BASICIO_HPP_ +#define BASICIO_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" + +// + standard includes +#include <string> +#include <vector> +#include <cstdio> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class definitions + + /*! + @brief An interface for simple binary IO. + + Designed to have semantics and names similar to those of C style FILE* + operations. Subclasses should all behave the same so that they can be + interchanged. + */ + class BasicIo + { + public: + //! BasicIo auto_ptr type + typedef std::auto_ptr<BasicIo> AutoPtr; + + //! Seek starting positions + enum Position { beg, cur, end }; + + //! @name Creators + //@{ + //! Destructor + virtual ~BasicIo() {} + //@} + + //! @name Manipulators + //@{ + /*! + @brief Open the IO source using the default access mode. The + default mode should allow for reading and writing. + + This method can also be used to "reopen" an IO source which will + flush any unwritten data and reset the IO position to the start. + Subclasses may provide custom methods to allow for + opening IO sources differently. + + @return 0 if successful;<BR> + Nonzero if failure. + */ + virtual int open() = 0; + /*! + @brief Close the IO source. After closing a BasicIo instance can not + be read or written. Closing flushes any unwritten data. It is + safe to call close on a closed instance. + @return 0 if successful;<BR> + Nonzero if failure. + */ + virtual int close() = 0; + /*! + @brief Write data to the IO source. Current IO position is advanced + by the number of bytes written. + @param data Pointer to data. Data must be at least \em wcount + bytes long + @param wcount Number of bytes to be written. + @return Number of bytes written to IO source successfully;<BR> + 0 if failure; + */ + virtual long write(const byte* data, long wcount) = 0; + /*! + @brief Write data that is read from another BasicIo instance to + the IO source. Current IO position is advanced by the number + of bytes written. + @param src Reference to another BasicIo instance. Reading start + at the source's current IO position + @return Number of bytes written to IO source successfully;<BR> + 0 if failure; + */ + virtual long write(BasicIo& src) = 0; + /*! + @brief Write one byte to the IO source. Current IO position is + advanced by one byte. + @param data The single byte to be written. + @return The value of the byte written if successful;<BR> + EOF if failure; + */ + virtual int putb(byte data) = 0; + /*! + @brief Read data from the IO source. Reading starts at the current + IO position and the position is advanced by the number of bytes + read. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return DataBuf instance containing the bytes read. Use the + DataBuf::size_ member to find the number of bytes read. + DataBuf::size_ will be 0 on failure. + */ + virtual DataBuf read(long rcount) = 0; + /*! + @brief Read data from the IO source. Reading starts at the current + IO position and the position is advanced by the number of bytes + read. + @param buf Pointer to a block of memory into which the read data + is stored. The memory block must be at least \em rcount bytes + long. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return Number of bytes read from IO source successfully;<BR> + 0 if failure; + */ + virtual long read(byte *buf, long rcount) = 0; + /*! + @brief Read one byte from the IO source. Current IO position is + advanced by one byte. + @return The byte read from the IO source if successful;<BR> + EOF if failure; + */ + virtual int getb() = 0; + /*! + @brief Remove all data from this object's IO source and then transfer + data from the \em src BasicIo object into this object. + + The source object is invalidated by this operation and should not be + used after this method returns. This method exists primarily to + be used with the BasicIo::temporary() method. + + @param src Reference to another BasicIo instance. The entire contents + of src are transferred to this object. The \em src object is + invalidated by the method. + @throw Error In case of failure + */ + virtual void transfer(BasicIo& src) = 0; + /*! + @brief Move the current IO position. + @param offset Number of bytes to move the position relative + to the starting position specified by \em pos + @param pos Position from which the seek should start + @return 0 if successful;<BR> + Nonzero if failure; + */ + virtual int seek(long offset, Position pos) = 0; + //@} + + //! @name Accessors + //@{ + /*! + @brief Get the current IO position. + @return Offset from the start of IO if successful;<BR> + -1 if failure; + */ + virtual long tell() const = 0; + /*! + @brief Get the current size of the IO source in bytes. + @return Size of the IO source in bytes;<BR> + -1 if failure; + */ + virtual long size() const = 0; + //!Returns true if the IO source is open, otherwise false. + virtual bool isopen() const = 0; + //!Returns 0 if the IO source is in a valid state, otherwise nonzero. + virtual int error() const = 0; + //!Returns true if the IO position has reach the end, otherwise false. + virtual bool eof() const = 0; + /*! + @brief Return the path to the IO resource. Often used to form + comprehensive error messages where only a BasicIo instance is + available. + */ + virtual std::string path() const =0; + /*! + @brief Returns a temporary data storage location. This is often + needed to rewrite an IO source. + + For example, data may be read from the original IO source, modified + in some way, and then saved to the temporary instance. After the + operation is complete, the BasicIo::transfer method can be used to + replace the original IO source with the modified version. Subclasses + are free to return any class that derives from BasicIo. + + @return An instance of BasicIo on success + @throw Error In case of failure + */ + virtual BasicIo::AutoPtr temporary() const = 0; + //@} + + protected: + //! @name Creators + //@{ + //! Default Constructor + BasicIo() {} + //@} + }; // class BasicIo + + /*! + @brief Utility class that closes a BasicIo instance upon destruction. + Meant to be used as a stack variable in functions that need to + ensure BasicIo instances get closed. Useful when functions return + errors from many locations. + */ + class IoCloser { + public: + //! @name Creators + //@{ + //! Constructor, takes a BasicIo reference + IoCloser(BasicIo &bio) : bio_(bio) {} + //! Destructor, closes the BasicIo reference + ~IoCloser() { close(); } + //@} + + //! @name Manipulators + //@{ + //! Close the BasicIo if it is open + void close() { if (bio_.isopen()) bio_.close(); } + //@} + + // DATA + //! The BasicIo reference + BasicIo &bio_; + private: + // Not implemented + //! Copy constructor + IoCloser(const IoCloser&); + //! Assignment operator + IoCloser& operator=(const IoCloser&); + }; // class IoCloser + + + /*! + @brief Provides binary file IO by implementing the BasicIo + interface. + */ + class FileIo : public BasicIo + { + public: + //! @name Creators + //@{ + /*! + @brief Constructor that accepts the file path on which IO will be + performed. The constructor does not open the file, and + therefore never failes. + @param path The full path of a file + */ + FileIo(const std::string& path); + //! Destructor. Flushes and closes an open file. + virtual ~FileIo(); + //@} + + //! @name Manipulators + //@{ + /*! + @brief Open the file using using the specified mode. + + This method can also be used to "reopen" a file which will flush any + unwritten data and reset the IO position to the start. Although + files can be opened in binary or text mode, this class has + only been tested carefully in binary mode. + + @param mode Specified that type of access allowed on the file. + Valid values match those of the C fopen command exactly. + @return 0 if successful;<BR> + Nonzero if failure. + */ + int open(const std::string& mode); + /*! + @brief Open the file using using the default access mode of "rb". + This method can also be used to "reopen" a file which will flush + any unwritten data and reset the IO position to the start. + @return 0 if successful;<BR> + Nonzero if failure. + */ + virtual int open(); + /*! + @brief Flush and unwritten data and close the file . It is + safe to call close on an already closed instance. + @return 0 if successful;<BR> + Nonzero if failure; + */ + virtual int close(); + /*! + @brief Write data to the file. The file position is advanced + by the number of bytes written. + @param data Pointer to data. Data must be at least \em wcount + bytes long + @param wcount Number of bytes to be written. + @return Number of bytes written to the file successfully;<BR> + 0 if failure; + */ + virtual long write(const byte* data, long wcount); + /*! + @brief Write data that is read from another BasicIo instance to + the file. The file position is advanced by the number + of bytes written. + @param src Reference to another BasicIo instance. Reading start + at the source's current IO position + @return Number of bytes written to the file successfully;<BR> + 0 if failure; + */ + virtual long write(BasicIo& src); + /*! + @brief Write one byte to the file. The file position is + advanced by one byte. + @param data The single byte to be written. + @return The value of the byte written if successful;<BR> + EOF if failure; + */ + virtual int putb(byte data); + /*! + @brief Read data from the file. Reading starts at the current + file position and the position is advanced by the number of + bytes read. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return DataBuf instance containing the bytes read. Use the + DataBuf::size_ member to find the number of bytes read. + DataBuf::size_ will be 0 on failure. + */ + virtual DataBuf read(long rcount); + /*! + @brief Read data from the file. Reading starts at the current + file position and the position is advanced by the number of + bytes read. + @param buf Pointer to a block of memory into which the read data + is stored. The memory block must be at least \em rcount bytes + long. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return Number of bytes read from the file successfully;<BR> + 0 if failure; + */ + virtual long read(byte *buf, long rcount); + /*! + @brief Read one byte from the file. The file position is + advanced by one byte. + @return The byte read from the file if successful;<BR> + EOF if failure; + */ + virtual int getb(); + /*! + @brief Remove the contents of the file and then transfer data from + the \em src BasicIo object into the empty file. + + This method is optimized to simply rename the source file if the + source object is another FileIo instance. The source BasicIo object + is invalidated by this operation and should not be used after this + method returns. This method exists primarily to be used with + the BasicIo::temporary() method. + + @param src Reference to another BasicIo instance. The entire contents + of src are transferred to this object. The \em src object is + invalidated by the method. + @throw Error In case of failure + */ + virtual void transfer(BasicIo& src); + /*! + @brief Move the current file position. + @param offset Number of bytes to move the file position + relative to the starting position specified by \em pos + @param pos Position from which the seek should start + @return 0 if successful;<BR> + Nonzero if failure; + */ + virtual int seek(long offset, Position pos); + //@} + + //! @name Accessors + //@{ + /*! + @brief Get the current file position. + @return Offset from the start of the file if successful;<BR> + -1 if failure; + */ + virtual long tell() const; + /*! + @brief Flush any buffered writes and get the current file size + in bytes. + @note On Win32 systems the file must be closed prior to calling this + function. + @return Size of the file in bytes;<BR> + -1 if failure; + */ + virtual long size() const; + //! Returns true if the file is open, otherwise false. + virtual bool isopen() const; + //! Returns 0 if the file is in a valid state, otherwise nonzero. + virtual int error() const; + //! Returns true if the file position has reach the end, otherwise false. + virtual bool eof() const; + //! Returns the path of the file + virtual std::string path() const; + /*! + @brief Returns a temporary data storage location. The actual type + returned depends upon the size of the file represented a FileIo + object. For small files, a MemIo is returned while for large files + a FileIo is returned. Callers should not rely on this behavior, + however, since it may change. + @return An instance of BasicIo on success + @throw Error If opening the temporary file fails + */ + virtual BasicIo::AutoPtr temporary() const; + //@} + + private: + // NOT IMPLEMENTED + //! Copy constructor + FileIo(FileIo& rhs); + //! Assignment operator + FileIo& operator=(const FileIo& rhs); + + // Enumeration + enum OpMode { opRead, opWrite, opSeek }; + + // DATA + std::string path_; + std::string openMode_; + FILE *fp_; + OpMode opMode_; + + // METHODS + /*! + @brief Switch to a new access mode, reopening the file if needed. + Optimized to only reopen the file when it is really necessary. + @param opMode The mode to switch to. + @return 0 if successful + */ + int switchMode(OpMode opMode); + + }; // class FileIo + + /*! + @brief Provides binary IO on blocks of memory by implementing the + BasicIo interface. The current implementation makes a copy of + any data passed to its constructors. If writes are performed, the + changed data can be retrieved using the read methods (since the + data used in construction is never modified). + + @note If read only usage of this class is common, it might be worth + creating a specialized readonly class or changing this one to + have a readonly mode. + */ + class MemIo : public BasicIo + { + public: + //! @name Creators + //@{ + //! Default constructor that results in an empty object + MemIo() { idx_ = 0; } + /*! + @brief Constructor that accepts a block of memory to be copied. + IO operations are performed on the copied memory. + @param data Pointer to data. Data must be at least \em size + bytes long + @param size Number of bytes to copy. + */ + MemIo(const byte* data, long size); + //! Destructor. Releases all managed memory + virtual ~MemIo() {} + //@} + + //! @name Manipulators + //@{ + /*! + @brief Memory IO is always open for reading and writing. This method + therefore only resets the IO position to the start. + + @return 0 + */ + virtual int open(); + /*! + @brief Does nothing on MemIo objects. + @return 0 + */ + virtual int close(); + /*! + @brief Write data to the memory block. If needed, the size of the + internal memory block is expanded. The IO position is advanced + by the number of bytes written. + @param data Pointer to data. Data must be at least \em wcount + bytes long + @param wcount Number of bytes to be written. + @return Number of bytes written to the memory block successfully;<BR> + 0 if failure; + */ + virtual long write(const byte* data, long wcount); + /*! + @brief Write data that is read from another BasicIo instance to + the memory block. If needed, the size of the internal memory + block is expanded. The IO position is advanced by the number + of bytes written. + @param src Reference to another BasicIo instance. Reading start + at the source's current IO position + @return Number of bytes written to the memory block successfully;<BR> + 0 if failure; + */ + virtual long write(BasicIo& src); + /*! + @brief Write one byte to the memory block. The IO position is + advanced by one byte. + @param data The single byte to be written. + @return The value of the byte written if successful;<BR> + EOF if failure; + */ + virtual int putb(byte data); + /*! + @brief Read data from the memory block. Reading starts at the current + IO position and the position is advanced by the number of + bytes read. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return DataBuf instance containing the bytes read. Use the + DataBuf::size_ member to find the number of bytes read. + DataBuf::size_ will be 0 on failure. + */ + virtual DataBuf read(long rcount); + /*! + @brief Read data from the memory block. Reading starts at the current + IO position and the position is advanced by the number of + bytes read. + @param buf Pointer to a block of memory into which the read data + is stored. The memory block must be at least \em rcount bytes + long. + @param rcount Maximum number of bytes to read. Fewer bytes may be + read if \em rcount bytes are not available. + @return Number of bytes read from the memory block successfully;<BR> + 0 if failure; + */ + virtual long read(byte *buf, long rcount); + /*! + @brief Read one byte from the memory block. The IO position is + advanced by one byte. + @return The byte read from the memory block if successful;<BR> + EOF if failure; + */ + virtual int getb(); + /*! + @brief Clear the memory clock and then transfer data from + the \em src BasicIo object into a new block of memory. + + This method is optimized to simply swap memory block if the source + object is another MemIo instance. The source BasicIo instance + is invalidated by this operation and should not be used after this + method returns. This method exists primarily to be used with + the BasicIo::temporary() method. + + @param src Reference to another BasicIo instance. The entire contents + of src are transferred to this object. The \em src object is + invalidated by the method. + @throw Error In case of failure + */ + virtual void transfer(BasicIo& src); + /*! + @brief Move the current IO position. + @param offset Number of bytes to move the IO position + relative to the starting position specified by \em pos + @param pos Position from which the seek should start + @return 0 if successful;<BR> + Nonzero if failure; + */ + virtual int seek(long offset, Position pos); + //@} + + //! @name Accessors + //@{ + /*! + @brief Get the current IO position. + @return Offset from the start of the memory block + */ + virtual long tell() const; + /*! + @brief Get the current memory buffer size in bytes. + @return Size of the in memory data in bytes;<BR> + -1 if failure; + */ + virtual long size() const; + //!Always returns true + virtual bool isopen() const; + //!Always returns 0 + virtual int error() const; + //!Returns true if the IO position has reach the end, otherwise false. + virtual bool eof() const; + //! Returns a dummy path, indicating that memory access is used + virtual std::string path() const; + /*! + @brief Returns a temporary data storage location. Currently returns + an empty MemIo object, but callers should not rely on this + behavior since it may change. + @return An instance of BasicIo + */ + virtual BasicIo::AutoPtr temporary() const; + //@} + private: + // NOT IMPLEMENTED + //! Copy constructor + MemIo(MemIo& rhs); + //! Assignment operator + MemIo& operator=(const MemIo& rhs); + + // Typedefs + typedef std::vector<byte> ByteVector; + + // DATA + ByteVector data_; + ByteVector::size_type idx_; + + // METHODS + void checkSize(long wcount); + }; // class MemIo +} // namespace Exiv2 + +#endif // #ifndef BASICIO_HPP_ diff --git a/src/plugins/exiv2/canonmn.cpp b/src/plugins/exiv2/canonmn.cpp @@ -0,0 +1,934 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: canonmn.cpp + Version: $Rev: 569 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 18-Feb-04, ahu: created + 07-Mar-04, ahu: isolated as a separate component + Credits: Canon MakerNote implemented according to the specification + "EXIF MakerNote of Canon" <http://www.burren.cx/david/canon.html> + by David Burren + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: canonmn.cpp 569 2005-05-28 05:48:43Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "canonmn.hpp" +#include "makernote.hpp" +#include "value.hpp" +#include "ifd.hpp" + +// + standard includes +#include <string> +#include <sstream> +#include <iomanip> +#include <algorithm> +#include <cassert> +#include <cstring> +#include <cmath> + +// Define DEBUG_MAKERNOTE to output debug information to std::cerr +#undef DEBUG_MAKERNOTE + +// ***************************************************************************** +// local declarations +namespace { + /* + @brief Convert Canon hex-based EV (modulo 0x20) to real number + Ported from Phil Harvey's Image::ExifTool::Canon::CanonEv + by Will Stokes + + 0x00 -> 0 + 0x0c -> 0.33333 + 0x10 -> 0.5 + 0x14 -> 0.66666 + 0x20 -> 1 + .. + 160 -> 5 + 128 -> 4 + 143 -> 4.46875 + */ + float canonEv(long val); +} + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + //! @cond IGNORE + CanonMakerNote::RegisterMn::RegisterMn() + { + MakerNoteFactory::registerMakerNote("Canon", "*", createCanonMakerNote); + + MakerNoteFactory::registerMakerNote( + canonIfdId, MakerNote::AutoPtr(new CanonMakerNote)); + MakerNoteFactory::registerMakerNote( + canonCs1IfdId, MakerNote::AutoPtr(new CanonMakerNote)); + MakerNoteFactory::registerMakerNote( + canonCs2IfdId, MakerNote::AutoPtr(new CanonMakerNote)); + MakerNoteFactory::registerMakerNote( + canonCfIfdId, MakerNote::AutoPtr(new CanonMakerNote)); + + ExifTags::registerMakerTagInfo(canonIfdId, tagInfo_); + ExifTags::registerMakerTagInfo(canonCs1IfdId, tagInfoCs1_); + ExifTags::registerMakerTagInfo(canonCs2IfdId, tagInfoCs2_); + ExifTags::registerMakerTagInfo(canonCfIfdId, tagInfoCf_); + } + //! @endcond + + // Canon MakerNote Tag Info + const TagInfo CanonMakerNote::tagInfo_[] = { + TagInfo(0x0000, "0x0000", "Unknown", canonIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0001, "CameraSettings1", "Various camera settings (1)", canonIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0002, "0x0002", "Unknown", canonIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0003, "0x0003", "Unknown", canonIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0004, "CameraSettings2", "Various camera settings (2)", canonIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0006, "ImageType", "Image type", canonIfdId, makerTags, asciiString, printValue), + TagInfo(0x0007, "FirmwareVersion", "Firmware version", canonIfdId, makerTags, asciiString, printValue), + TagInfo(0x0008, "ImageNumber", "Image number", canonIfdId, makerTags, unsignedLong, print0x0008), + TagInfo(0x0009, "OwnerName", "Owner Name", canonIfdId, makerTags, asciiString, printValue), + TagInfo(0x000c, "SerialNumber", "Camera serial number", canonIfdId, makerTags, unsignedLong, print0x000c), + TagInfo(0x000d, "0x000d", "Unknown", canonIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000f, "CustomFunctions", "Custom Functions", canonIfdId, makerTags, unsignedShort, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownCanonMakerNoteTag)", "Unknown CanonMakerNote tag", canonIfdId, makerTags, invalidTypeId, printValue) + }; + + // Canon Camera Settings 1 Tag Info + const TagInfo CanonMakerNote::tagInfoCs1_[] = { + TagInfo(0x0001, "Macro", "Macro mode", canonCs1IfdId, makerTags, unsignedShort, printCs10x0001), + TagInfo(0x0002, "Selftimer", "Self timer", canonCs1IfdId, makerTags, unsignedShort, printCs10x0002), + TagInfo(0x0003, "Quality", "Quality", canonCs1IfdId, makerTags, unsignedShort, printCs10x0003), + TagInfo(0x0004, "FlashMode", "Flash mode setting", canonCs1IfdId, makerTags, unsignedShort, printCs10x0004), + TagInfo(0x0005, "DriveMode", "Drive mode setting", canonCs1IfdId, makerTags, unsignedShort, printCs10x0005), + TagInfo(0x0006, "0x0006", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0007, "FocusMode", "Focus mode setting", canonCs1IfdId, makerTags, unsignedShort, printCs10x0007), + TagInfo(0x0008, "0x0008", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0009, "0x0009", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000a, "ImageSize", "Image size", canonCs1IfdId, makerTags, unsignedShort, printCs10x000a), + TagInfo(0x000b, "EasyMode", "Easy shooting mode", canonCs1IfdId, makerTags, unsignedShort, printCs10x000b), + TagInfo(0x000c, "DigitalZoom", "Digital zoom", canonCs1IfdId, makerTags, unsignedShort, printCs10x000c), + TagInfo(0x000d, "Contrast", "Contrast setting", canonCs1IfdId, makerTags, unsignedShort, printCs1Lnh), + TagInfo(0x000e, "Saturation", "Saturation setting", canonCs1IfdId, makerTags, unsignedShort, printCs1Lnh), + TagInfo(0x000f, "Sharpness", "Sharpness setting", canonCs1IfdId, makerTags, unsignedShort, printCs1Lnh), + TagInfo(0x0010, "ISOSpeed", "ISO speed setting", canonCs1IfdId, makerTags, unsignedShort, printCs10x0010), + TagInfo(0x0011, "MeteringMode", "Metering mode setting", canonCs1IfdId, makerTags, unsignedShort, printCs10x0011), + TagInfo(0x0012, "FocusType", "Focus type setting", canonCs1IfdId, makerTags, unsignedShort, printCs10x0012), + TagInfo(0x0013, "AFPoint", "AF point selected", canonCs1IfdId, makerTags, unsignedShort, printCs10x0013), + TagInfo(0x0014, "ExposureProgram", "Exposure mode setting", canonCs1IfdId, makerTags, unsignedShort, printCs10x0014), + TagInfo(0x0015, "0x0015", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0016, "0x0016", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0017, "Lens", "'long' and 'short' focal length of lens (in 'focal units') and 'focal units' per mm", canonCs1IfdId, makerTags, unsignedShort, printCs1Lens), + TagInfo(0x0018, "0x0018", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0019, "0x0019", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x001a, "0x001a", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x001b, "0x001b", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x001c, "FlashActivity", "Flash activity", canonCs1IfdId, makerTags, unsignedShort, printCs10x001c), + TagInfo(0x001d, "FlashDetails", "Flash details", canonCs1IfdId, makerTags, unsignedShort, printCs10x001d), + TagInfo(0x001e, "0x001e", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x001f, "0x001f", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0020, "FocusMode", "Focus mode setting", canonCs1IfdId, makerTags, unsignedShort, printCs10x0020), + TagInfo(0x0021, "0x0021", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0022, "0x0022", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0023, "0x0023", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0024, "0x0024", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0025, "0x0025", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0026, "0x0026", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0027, "0x0027", "Unknown", canonCs1IfdId, makerTags, unsignedShort, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownCanonCs1Tag)", "Unknown Canon Camera Settings 1 tag", canonCs1IfdId, makerTags, invalidTypeId, printValue) + }; + + // Canon Camera Settings 2 Tag Info + const TagInfo CanonMakerNote::tagInfoCs2_[] = { + TagInfo(0x0001, "0x0001", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0002, "ISOSpeed", "ISO speed used", canonCs2IfdId, makerTags, unsignedShort, printCs20x0002), + TagInfo(0x0003, "0x0003", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0004, "0x0004", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0005, "0x0005", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0006, "0x0006", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0007, "WhiteBalance", "White balance setting", canonCs2IfdId, makerTags, unsignedShort, printCs20x0007), + TagInfo(0x0008, "0x0008", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0009, "Sequence", "Sequence number (if in a continuous burst)", canonCs2IfdId, makerTags, unsignedShort, printCs20x0009), + TagInfo(0x000a, "0x000a", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000b, "0x000b", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000c, "0x000c", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000d, "0x000d", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000e, "AFPointUsed", "AF point used", canonCs2IfdId, makerTags, unsignedShort, printCs20x000e), + TagInfo(0x000f, "FlashBias", "Flash bias", canonCs2IfdId, makerTags, unsignedShort, printCs20x000f), + TagInfo(0x0010, "0x0010", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0011, "0x0011", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0012, "0x0012", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0013, "SubjectDistance", "Subject distance (units are not clear)", canonCs2IfdId, makerTags, unsignedShort, printCs20x0013), + TagInfo(0x0014, "0x0014", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0015, "0x0015", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0016, "0x0016", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0017, "0x0017", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0018, "0x0018", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0019, "0x0019", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x001a, "0x001a", "Unknown", canonCs2IfdId, makerTags, unsignedShort, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownCanonCs2Tag)", "Unknown Canon Camera Settings 2 tag", canonCs2IfdId, makerTags, invalidTypeId, printValue) + }; + + // Canon Custom Function Tag Info + const TagInfo CanonMakerNote::tagInfoCf_[] = { + TagInfo(0x0001, "NoiseReduction", "Long exposure noise reduction", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0002, "ShutterAeLock", "Shutter/AE lock buttons", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0003, "MirrorLockup", "Mirror lockup", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0004, "ExposureLevelIncrements", "Tv/Av and exposure level", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0005, "AFAssist", "AF assist light", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0006, "FlashSyncSpeedAv", "Shutter speed in Av mode", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0007, "AEBSequence", "AEB sequence/auto cancellation", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0008, "ShutterCurtainSync", "Shutter curtain sync", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0009, "LensAFStopButton", "Lens AF stop button Fn. Switch", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000a, "FillFlashAutoReduction", "Auto reduction of fill flash", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000b, "MenuButtonReturn", "Menu button return position", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000c, "SetButtonFunction", "SET button func. when shooting", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000d, "SensorCleaning", "Sensor cleaning", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000e, "SuperimposedDisplay", "Superimposed display", canonCfIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x000f, "ShutterReleaseNoCFCard", "Shutter Release W/O CF Card", canonCfIfdId, makerTags, unsignedShort, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownCanonCfTag)", "Unknown Canon Custom Function tag", canonCfIfdId, makerTags, invalidTypeId, printValue) + }; + + int CanonMakerNote::read(const byte* buf, + long len, + ByteOrder byteOrder, + long offset) + { + int rc = IfdMakerNote::read(buf, len, byteOrder, offset); + if (rc) return rc; + + // Decode camera settings 1 and add settings as additional entries + Entries::iterator cs = ifd_.findTag(0x0001); + if (cs != ifd_.end() && cs->type() == unsignedShort) { + for (uint16_t c = 1; cs->count() > c; ++c) { + if (c == 23 && cs->count() > 25) { + // Pack related lens info into one tag + addCsEntry(canonCs1IfdId, c, cs->offset() + c*2, + cs->data() + c*2, 3); + c += 2; + } + else { + addCsEntry(canonCs1IfdId, c, cs->offset() + c*2, + cs->data() + c*2, 1); + } + } + // Discard the original entry + ifd_.erase(cs); + } + + // Decode camera settings 2 and add settings as additional entries + cs = ifd_.findTag(0x0004); + if (cs != ifd_.end() && cs->type() == unsignedShort) { + for (uint16_t c = 1; cs->count() > c; ++c) { + addCsEntry(canonCs2IfdId, c, cs->offset() + c*2, + cs->data() + c*2, 1); + } + // Discard the original entry + ifd_.erase(cs); + } + + // Decode custom functions and add each as an additional entry + cs = ifd_.findTag(0x000f); + if (cs != ifd_.end() && cs->type() == unsignedShort) { + for (uint16_t c = 1; cs->count() > c; ++c) { + addCsEntry(canonCfIfdId, c, cs->offset() + c*2, + cs->data() + c*2, 1); + } + // Discard the original entry + ifd_.erase(cs); + } + + // Copy remaining ifd entries + entries_.insert(entries_.begin(), ifd_.begin(), ifd_.end()); + + // Set idx + int idx = 0; + Entries::iterator e = entries_.end(); + for (Entries::iterator i = entries_.begin(); i != e; ++i) { + i->setIdx(++idx); + } + + return 0; + } + + void CanonMakerNote::addCsEntry(IfdId ifdId, + uint16_t tag, + long offset, + const byte* data, + int count) + { + Entry e(false); + e.setIfdId(ifdId); + e.setTag(tag); + e.setOffset(offset); + e.setValue(unsignedShort, count, data, 2*count); + add(e); + } + + void CanonMakerNote::add(const Entry& entry) + { + assert(alloc_ == entry.alloc()); + assert( entry.ifdId() == canonIfdId + || entry.ifdId() == canonCs1IfdId + || entry.ifdId() == canonCs2IfdId + || entry.ifdId() == canonCfIfdId); + // allow duplicates + entries_.push_back(entry); + } + + long CanonMakerNote::copy(byte* buf, ByteOrder byteOrder, long offset) + { + if (byteOrder_ == invalidByteOrder) byteOrder_ = byteOrder; + + assert(ifd_.alloc()); + ifd_.clear(); + + // Add all standard Canon entries to the IFD + Entries::const_iterator end = entries_.end(); + for (Entries::const_iterator i = entries_.begin(); i != end; ++i) { + if (i->ifdId() == canonIfdId) { + ifd_.add(*i); + } + } + // Collect camera settings 1 entries and add the original Canon tag + Entry cs1; + if (assemble(cs1, canonCs1IfdId, 0x0001, byteOrder_)) { + ifd_.erase(0x0001); + ifd_.add(cs1); + } + // Collect camera settings 2 entries and add the original Canon tag + Entry cs2; + if (assemble(cs2, canonCs2IfdId, 0x0004, byteOrder_)) { + ifd_.erase(0x0004); + ifd_.add(cs2); + } + // Collect custom function entries and add the original Canon tag + Entry cf; + if (assemble(cf, canonCfIfdId, 0x000f, byteOrder_)) { + ifd_.erase(0x000f); + ifd_.add(cf); + } + + return IfdMakerNote::copy(buf, byteOrder_, offset); + } // CanonMakerNote::copy + + void CanonMakerNote::updateBase(byte* pNewBase) + { + byte* pBase = ifd_.updateBase(pNewBase); + if (absOffset_ && !alloc_) { + Entries::iterator end = entries_.end(); + for (Entries::iterator pos = entries_.begin(); pos != end; ++pos) { + pos->updateBase(pBase, pNewBase); + } + } + } // CanonMakerNote::updateBase + + long CanonMakerNote::size() const + { + Ifd ifd(canonIfdId, 0, alloc_); // offset doesn't matter + + // Add all standard Canon entries to the IFD + Entries::const_iterator end = entries_.end(); + for (Entries::const_iterator i = entries_.begin(); i != end; ++i) { + if (i->ifdId() == canonIfdId) { + ifd.add(*i); + } + } + // Collect camera settings 1 entries and add the original Canon tag + Entry cs1(alloc_); + if (assemble(cs1, canonCs1IfdId, 0x0001, littleEndian)) { + ifd.erase(0x0001); + ifd.add(cs1); + } + // Collect camera settings 2 entries and add the original Canon tag + Entry cs2(alloc_); + if (assemble(cs2, canonCs2IfdId, 0x0004, littleEndian)) { + ifd.erase(0x0004); + ifd.add(cs2); + } + // Collect custom function entries and add the original Canon tag + Entry cf(alloc_); + if (assemble(cf, canonCfIfdId, 0x000f, littleEndian)) { + ifd.erase(0x000f); + ifd.add(cf); + } + + return headerSize() + ifd.size() + ifd.dataSize(); + } // CanonMakerNote::size + + long CanonMakerNote::assemble(Entry& e, + IfdId ifdId, + uint16_t tag, + ByteOrder byteOrder) const + { + DataBuf buf(1024); + memset(buf.pData_, 0x0, 1024); + uint16_t len = 0; + Entries::const_iterator end = entries_.end(); + for (Entries::const_iterator i = entries_.begin(); i != end; ++i) { + if (i->ifdId() == ifdId) { + uint16_t pos = i->tag() * 2; + uint16_t size = pos + static_cast<uint16_t>(i->size()); + assert(size <= 1024); + memcpy(buf.pData_ + pos, i->data(), i->size()); + if (len < size) len = size; + } + } + if (len > 0) { + // Number of shorts in the buffer (rounded up) + uint16_t s = (len+1) / 2; + us2Data(buf.pData_, s*2, byteOrder); + + e.setIfdId(canonIfdId); + e.setIdx(0); // don't care + e.setTag(tag); + e.setOffset(0); // will be calculated when the IFD is written + e.setValue(unsignedShort, s, buf.pData_, s*2); + } + return len; + } // CanonMakerNote::assemble + + Entries::const_iterator CanonMakerNote::findIdx(int idx) const + { + return std::find_if(entries_.begin(), entries_.end(), + FindEntryByIdx(idx)); + } + + CanonMakerNote::CanonMakerNote(bool alloc) + : IfdMakerNote(canonIfdId, alloc) + { + } + + CanonMakerNote::CanonMakerNote(const CanonMakerNote& rhs) + : IfdMakerNote(rhs) + { + entries_ = rhs.entries_; + } + + CanonMakerNote::AutoPtr CanonMakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + CanonMakerNote* CanonMakerNote::create_(bool alloc) const + { + return new CanonMakerNote(alloc); + } + + CanonMakerNote::AutoPtr CanonMakerNote::clone() const + { + return AutoPtr(clone_()); + } + + CanonMakerNote* CanonMakerNote::clone_() const + { + return new CanonMakerNote(*this); + } + + std::ostream& CanonMakerNote::print0x0008(std::ostream& os, + const Value& value) + { + std::string n = value.toString(); + return os << n.substr(0, n.length() - 4) << "-" + << n.substr(n.length() - 4); + } + + std::ostream& CanonMakerNote::print0x000c(std::ostream& os, + const Value& value) + { + std::istringstream is(value.toString()); + uint32_t l; + is >> l; + return os << std::setw(4) << std::setfill('0') << std::hex + << ((l & 0xffff0000) >> 16) + << std::setw(5) << std::setfill('0') << std::dec + << (l & 0x0000ffff); + } + + std::ostream& CanonMakerNote::printCs10x0001(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 1: os << "On"; break; + case 2: os << "Off"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0002(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + if (l == 0) { + os << "Off"; + } + else { + os << l / 10.0 << " s"; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0003(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 2: os << "Normal"; break; + case 3: os << "Fine"; break; + case 5: os << "Superfine"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0004(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "Off"; break; + case 1: os << "Auto"; break; + case 2: os << "On"; break; + case 3: os << "Red-eye"; break; + case 4: os << "Slow sync"; break; + case 5: os << "Auto + red-eye"; break; + case 6: os << "On + red-eye"; break; + case 16: os << "External"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0005(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "Single / timer"; break; + case 1: os << "Continuous"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0007(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "One shot"; break; + case 1: os << "AI servo"; break; + case 2: os << "AI Focus"; break; + case 3: os << "MF"; break; + case 4: os << "Single"; break; + case 5: os << "Continuous"; break; + case 6: os << "MF"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x000a(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "Large"; break; + case 1: os << "Medium"; break; + case 2: os << "Small"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x000b(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "Full auto"; break; + case 1: os << "Manual"; break; + case 2: os << "Landscape"; break; + case 3: os << "Fast shutter"; break; + case 4: os << "Slow shutter"; break; + case 5: os << "Night"; break; + case 6: os << "B&W"; break; + case 7: os << "Sepia"; break; + case 8: os << "Portrait"; break; + case 9: os << "Sports"; break; + case 10: os << "Macro / close-up"; break; + case 11: os << "Pan focus"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x000c(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "None"; break; + case 1: os << "2x"; break; + case 2: os << "4x"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs1Lnh(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0xffff: os << "Low"; break; + case 0x0000: os << "Normal"; break; + case 0x0001: os << "High"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0010(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "n/a"; break; + case 15: os << "Auto"; break; + case 16: os << "50"; break; + case 17: os << "100"; break; + case 18: os << "200"; break; + case 19: os << "400"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0011(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 3: os << "Evaluative"; break; + case 4: os << "Partial"; break; + case 5: os << "Center weighted"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0012(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "Manual"; break; + case 1: os << "Auto"; break; + case 3: os << "Close-up (macro)"; break; + case 8: os << "Locked (pan mode)"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0013(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0x3000: os << "None (MF)"; break; + case 0x3001: os << "Auto-selected"; break; + case 0x3002: os << "Right"; break; + case 0x3003: os << "Center"; break; + case 0x3004: os << "Left"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0014(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "Easy shooting"; break; + case 1: os << "Program"; break; + case 2: os << "Shutter priority"; break; + case 3: os << "Aperture priority"; break; + case 4: os << "Manual"; break; + case 5: os << "A-DEP"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x001c(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "Did not fire"; break; + case 1: os << "Fired"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x001d(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + bool coma = false; + if (l & 0x4000) { + if (coma) os << ", "; + os << "External TTL"; + coma = true; + } + if (l & 0x2000) { + if (coma) os << ", "; + os << "Internal flash"; + coma = true; + } + if (l & 0x0800) { + if (coma) os << ", "; + os << "FP sync used"; + coma = true; + } + if (l & 0x0080) { + if (coma) os << ", "; + os << "Rear curtain sync used"; + coma = true; + } + if (l & 0x0010) { + if (coma) os << ", "; + os << "FP sync enabled"; + coma = true; + } + return os; + } + + std::ostream& CanonMakerNote::printCs10x0020(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "Single"; break; + case 1: os << "Continuous"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs1Lens(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + if (value.count() < 3) return os << value; + + float fu = value.toFloat(2); + float len1 = value.toLong(0) / fu; + float len2 = value.toLong(1) / fu; + std::ostringstream oss; + oss.copyfmt(os); + os << std::fixed << std::setprecision(1) + << len2 << " - " << len1 << " mm"; + os.copyfmt(oss); + return os; + } + + std::ostream& CanonMakerNote::printCs20x0002(std::ostream& os, + const Value& value) + { + // Ported from Exiftool by Will Stokes + return os << exp(canonEv(value.toLong()) * log(2.0)) * 100.0 / 32.0; + } + + std::ostream& CanonMakerNote::printCs20x0007(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0: os << "Auto"; break; + case 1: os << "Sunny"; break; + case 2: os << "Cloudy"; break; + case 3: os << "Tungsten"; break; + case 4: os << "Fluorescent"; break; + case 5: os << "Flash"; break; + case 6: os << "Custom"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs20x0009(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + os << l << ""; + // Todo: determine unit + return os; + } + + std::ostream& CanonMakerNote::printCs20x000e(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + long num = (l & 0xf000) >> 12; + os << num << " focus points; "; + long used = l & 0x0fff; + if (used == 0) { + os << "none"; + } + else { + bool coma = false; + if (l & 0x0004) { + if (coma) os << ", "; + os << "left"; + coma = true; + } + if (l & 0x0002) { + if (coma) os << ", "; + os << "center"; + coma = true; + } + if (l & 0x0001) { + if (coma) os << ", "; + os << "right"; + coma = true; + } + } + os << " used"; + return os; + } + + std::ostream& CanonMakerNote::printCs20x000f(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + switch (l) { + case 0xffc0: os << "-2 EV"; break; + case 0xffcc: os << "-1.67 EV"; break; + case 0xffd0: os << "-1.50 EV"; break; + case 0xffd4: os << "-1.33 EV"; break; + case 0xffe0: os << "-1 EV"; break; + case 0xffec: os << "-0.67 EV"; break; + case 0xfff0: os << "-0.50 EV"; break; + case 0xfff4: os << "-0.33 EV"; break; + case 0x0000: os << "0 EV"; break; + case 0x000c: os << "0.33 EV"; break; + case 0x0010: os << "0.50 EV"; break; + case 0x0014: os << "0.67 EV"; break; + case 0x0020: os << "1 EV"; break; + case 0x002c: os << "1.33 EV"; break; + case 0x0030: os << "1.50 EV"; break; + case 0x0034: os << "1.67 EV"; break; + case 0x0040: os << "2 EV"; break; + default: os << "(" << l << ")"; break; + } + return os; + } + + std::ostream& CanonMakerNote::printCs20x0013(std::ostream& os, + const Value& value) + { + if (value.typeId() != unsignedShort) return os << value; + long l = value.toLong(); + if (l == 0xffff) { + os << "Infinite"; + } + else { + os << l << ""; + } + return os; + } + +// ***************************************************************************** +// free functions + + MakerNote::AutoPtr createCanonMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset) + { + return MakerNote::AutoPtr(new CanonMakerNote(alloc)); + } + +} // namespace Exiv2 + +// ***************************************************************************** +// local definitions +namespace { + + float canonEv(long val) + { + // temporarily remove sign + int sign = 1; + if (val < 0) { + sign = -1; + val = -val; + } + // remove fraction + float frac = static_cast<float>(val & 0x1f); + val -= long(frac); + // convert 1/3 (0x0c) and 2/3 (0x14) codes + if (frac == 0x0c) { + frac = 32.0f / 3; + } + else if (frac == 0x14) { + frac = 64.0f / 3; + } + return sign * (val + frac) / 32.0f; + } + +} diff --git a/src/plugins/exiv2/canonmn.hpp b/src/plugins/exiv2/canonmn.hpp @@ -0,0 +1,238 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file canonmn.hpp + @brief Canon MakerNote implemented according to the specification + <a href="http://www.burren.cx/david/canon.html"> + EXIF MakerNote of Canon</a> by David Burren<br> + and with reference to tag information from + <a href="http://www.sno.phy.queensu.ca/~phil/exiftool/"> + ExifTool</a> by Phil Harvey + @version $Rev: 569 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 18-Feb-04, ahu: created<BR> + 07-Mar-04, ahu: isolated as a separate component + */ +#ifndef CANONMN_HPP_ +#define CANONMN_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "makernote.hpp" +#include "tags.hpp" + +// + standard includes +#include <string> +#include <iosfwd> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class Value; + +// ***************************************************************************** +// free functions + + /*! + @brief Return an auto-pointer to a newly created empty MakerNote + initialized to operate in the memory management model indicated. + The caller owns this copy and the auto-pointer ensures that it + will be deleted. + + @param alloc Memory management model for the new MakerNote. Determines if + memory required to store data should be allocated and deallocated + (true) or not (false). If false, only pointers to the buffer + provided to read() will be kept. See Ifd for more background on + this concept. + @param buf Pointer to the makernote character buffer (not used). + @param len Length of the makernote character buffer (not used). + @param byteOrder Byte order in which the Exif data (and possibly the + makernote) is encoded (not used). + @param offset Offset from the start of the TIFF header of the makernote + buffer (not used). + + @return An auto-pointer to a newly created empty MakerNote. The caller + owns this copy and the auto-pointer ensures that it will be + deleted. + */ + MakerNote::AutoPtr createCanonMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset); + +// ***************************************************************************** +// class definitions + + //! MakerNote for Canon cameras + class CanonMakerNote : public IfdMakerNote { + public: + //! Shortcut for a %CanonMakerNote auto pointer. + typedef std::auto_ptr<CanonMakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to choose whether or not memory management + is required for the makernote entries. + */ + CanonMakerNote(bool alloc =true); + //! Copy constructor + CanonMakerNote(const CanonMakerNote& rhs); + //! Virtual destructor + virtual ~CanonMakerNote() {} + //@} + + //! @name Manipulators + //@{ + int read(const byte* buf, + long len, + ByteOrder byteOrder, + long offset); + long copy(byte* buf, ByteOrder byteOrder, long offset); + void add(const Entry& entry); + Entries::iterator begin() { return entries_.begin(); } + Entries::iterator end() { return entries_.end(); } + void updateBase(byte* pNewBase); + //@} + + //! @name Accessors + //@{ + Entries::const_iterator begin() const { return entries_.begin(); } + Entries::const_iterator end() const { return entries_.end(); } + Entries::const_iterator findIdx(int idx) const; + long size() const; + AutoPtr create(bool alloc =true) const; + AutoPtr clone() const; + //@} + + //! @name Print functions for Canon %MakerNote tags + //@{ + //! Print the image number + static std::ostream& print0x0008(std::ostream& os, const Value& value); + //! Print the serial number of the camera + static std::ostream& print0x000c(std::ostream& os, const Value& value); + + //! Macro mode + static std::ostream& printCs10x0001(std::ostream& os, const Value& value); + //! Self timer + static std::ostream& printCs10x0002(std::ostream& os, const Value& value); + //! Quality + static std::ostream& printCs10x0003(std::ostream& os, const Value& value); + //! Flash mode + static std::ostream& printCs10x0004(std::ostream& os, const Value& value); + //! Drive mode + static std::ostream& printCs10x0005(std::ostream& os, const Value& value); + //! Focus mode (G1 seems to use field 32 in preference to this) + static std::ostream& printCs10x0007(std::ostream& os, const Value& value); + //! Image size + static std::ostream& printCs10x000a(std::ostream& os, const Value& value); + //! Easy shooting + static std::ostream& printCs10x000b(std::ostream& os, const Value& value); + //! Digital zoom + static std::ostream& printCs10x000c(std::ostream& os, const Value& value); + //! ISO + static std::ostream& printCs10x0010(std::ostream& os, const Value& value); + //! Metering mode + static std::ostream& printCs10x0011(std::ostream& os, const Value& value); + //! Focus type + static std::ostream& printCs10x0012(std::ostream& os, const Value& value); + //! AF point selected + static std::ostream& printCs10x0013(std::ostream& os, const Value& value); + //! Exposure mode + static std::ostream& printCs10x0014(std::ostream& os, const Value& value); + //! Flash activity + static std::ostream& printCs10x001c(std::ostream& os, const Value& value); + //! Flash details + static std::ostream& printCs10x001d(std::ostream& os, const Value& value); + //! Focus mode (G1 seems to use this in preference to field 7) + static std::ostream& printCs10x0020(std::ostream& os, const Value& value); + //! Low, normal, high print function + static std::ostream& printCs1Lnh(std::ostream& os, const Value& value); + //! Camera lens information + static std::ostream& printCs1Lens(std::ostream& os, const Value& value); + //! ISO speed used + static std::ostream& printCs20x0002(std::ostream& os, const Value& value); + //! White balance + static std::ostream& printCs20x0007(std::ostream& os, const Value& value); + //! Sequence number + static std::ostream& printCs20x0009(std::ostream& os, const Value& value); + //! AF point used + static std::ostream& printCs20x000e(std::ostream& os, const Value& value); + //! Flash bias + static std::ostream& printCs20x000f(std::ostream& os, const Value& value); + //! Subject distance + static std::ostream& printCs20x0013(std::ostream& os, const Value& value); + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct RegisterMn { + RegisterMn(); + }; + //! @endcond + + private: + //! @name Manipulators + //@{ + //! Add a camera settings entry to the makernote entries + void addCsEntry(IfdId ifdId, + uint16_t tag, + long offset, + const byte* data, + int count); + //@} + + //! @name Accessors + //@{ + //! Assemble special Canon entries into an entry with the original tag + long assemble(Entry& e, + IfdId ifdId, + uint16_t tag, + ByteOrder byteOrder) const; + //! Internal virtual create function. + CanonMakerNote* create_(bool alloc =true) const; + //! Internal virtual copy constructor. + CanonMakerNote* clone_() const; + //@} + + // DATA + //! Container to store Makernote entries (instead of Ifd) + Entries entries_; + + //! Tag information + static const TagInfo tagInfo_[]; + static const TagInfo tagInfoCs1_[]; + static const TagInfo tagInfoCs2_[]; + static const TagInfo tagInfoCf_[]; + + }; // class CanonMakerNote + + static CanonMakerNote::RegisterMn registerCanonMakerNote; +} // namespace Exiv2 + +#endif // #ifndef CANONMN_HPP_ diff --git a/src/plugins/exiv2/datasets.cpp b/src/plugins/exiv2/datasets.cpp @@ -0,0 +1,392 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: datasets.cpp + Version: $Rev: 560 $ + Author(s): Brad Schick (brad) <brad@robotbattle.com> + History: 24-Jul-04, brad: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: datasets.cpp 560 2005-04-17 11:51:32Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "datasets.hpp" +#include "error.hpp" +#include "types.hpp" +#include "value.hpp" +#include "metadatum.hpp" + +#include <iostream> +#include <iomanip> +#include <sstream> + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + DataSet::DataSet( + uint16_t number, + const char* name, + const char* desc, + bool mandatory, + bool repeatable, + uint32_t minbytes, + uint32_t maxbytes, + TypeId type, + uint16_t recordId, + const char* photoshop + ) + : number_(number), name_(name), desc_(desc), mandatory_(mandatory), + repeatable_(repeatable), minbytes_(minbytes), maxbytes_(maxbytes), + type_(type), recordId_(recordId), photoshop_(photoshop) + { + } + + RecordInfo::RecordInfo( + uint16_t recordId, + const char* name, + const char* desc + ) + : recordId_(recordId), name_(name), desc_(desc) + { + } + + const RecordInfo IptcDataSets::recordInfo_[] = { + RecordInfo(IptcDataSets::invalidRecord, "(invalid)", "(invalid)"), + RecordInfo(IptcDataSets::envelope, "Envelope", "IIM envelope record"), + RecordInfo(IptcDataSets::application2, "Application2", "IIM application record 2"), + }; + + static const DataSet envelopeRecord[] = { + DataSet(IptcDataSets::ModelVersion, "ModelVersion", "Version of IIM part 1", true, false, 2, 2, Exiv2::unsignedShort, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::Destination, "Destination", "Routing information", false, true, 0, 1024, Exiv2::string, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::FileFormat, "FileFormat", "IIM appendix A file format", true, false, 2, 2, Exiv2::unsignedShort, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::FileVersion, "FileVersion", "File format version", true, false, 2, 2, Exiv2::unsignedShort, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::ServiceId, "ServiceId", "Identifies the provider and product", true, false, 0, 10, Exiv2::string, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::EnvelopeNumber, "EnvelopeNumber", "Combined unique identification", true, false, 8, 8, Exiv2::string, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::ProductId, "ProductId", "Identifies service subset", false, true, 0, 32, Exiv2::string, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::EnvelopePriority, "EnvelopePriority", "Envelope handling priority", false, false, 1, 1, Exiv2::string, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::DateSent, "DateSent", "Date material was sent", true, false, 8, 8, Exiv2::date, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::TimeSent, "TimeSent", "Time material was sent", false, false, 11, 11, Exiv2::time, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::CharacterSet, "CharacterSet", "Specifies character sets", false, false, 0, 32, Exiv2::undefined, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::UNO, "UNO", "Unique Name of Object", false, false, 14, 80, Exiv2::string, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::ARMId, "ARMId", "Abstract Relationship Method identifier", false, false, 2, 2, Exiv2::unsignedShort, IptcDataSets::envelope, ""), + DataSet(IptcDataSets::ARMVersion, "ARMVersion", "Abstract Relationship Method version", false, false, 2, 2, Exiv2::unsignedShort, IptcDataSets::envelope, ""), + DataSet(0xffff, "(Invalid)", "(Invalid)", false, false, 0, 0, Exiv2::unsignedShort, IptcDataSets::envelope, "") + }; + + static const DataSet application2Record[] = { + DataSet(IptcDataSets::RecordVersion, "RecordVersion", "Version of IIM part 2", true, false, 2, 2, Exiv2::unsignedShort, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ObjectType, "ObjectType", "IIM appendix G object type", false, false, 3, 67, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ObjectAttribute, "ObjectAttribute", "IIM appendix G object attribute", false, true, 4, 68, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ObjectName, "ObjectName", "Shorthand reference of content", false, false, 0, 64, Exiv2::string, IptcDataSets::application2, "Document title"), + DataSet(IptcDataSets::EditStatus, "EditStatus", "Content status", false, false, 0, 64, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::EditorialUpdate, "EditorialUpdate", "Indicates the type of update", false, false, 2, 2, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::Urgency, "Urgency", "Editorial urgency of content", false, false, 1, 1, Exiv2::string, IptcDataSets::application2, "Urgency"), + DataSet(IptcDataSets::Subject, "Subject", "Structured definition of the subject", false, true, 13, 236, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::Category, "Category", "Identifies the subject", false, false, 0, 3, Exiv2::string, IptcDataSets::application2, "Category"), + DataSet(IptcDataSets::SuppCategory, "SuppCategory", "Refines the subject", false, true, 0, 32, Exiv2::string, IptcDataSets::application2, "Supplemental Categories"), + DataSet(IptcDataSets::FixtureId, "FixtureId", "Identifies content that recurs", false, false, 0, 32, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::Keywords, "Keywords", "Information retrieval words", false, true, 0, 64, Exiv2::string, IptcDataSets::application2, "Keywords"), + DataSet(IptcDataSets::LocationCode, "LocationCode", "ISO country code for content", false, true, 3, 3, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::LocationName, "LocationName", "Full country name for content", false, true, 0, 64, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ReleaseDate, "ReleaseDate", "Earliest intended usable date", false, false, 8, 8, Exiv2::date, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ReleaseTime, "ReleaseTime", "Earliest intended usable time", false, false, 11, 11, Exiv2::time, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ExpirationDate, "ExpirationDate", "Latest intended usable date", false, false, 8, 8, Exiv2::date, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ExpirationTime, "ExpirationTime", "Latest intended usable time", false, false, 11, 11, Exiv2::time, IptcDataSets::application2, ""), + DataSet(IptcDataSets::SpecialInstructions, "SpecialInstructions", "Editorial usage instructions", false, false, 0, 256, Exiv2::string, IptcDataSets::application2, "Instructions"), + DataSet(IptcDataSets::ActionAdvised, "ActionAdvised", "Action provided to previous data", false, false, 2, 2, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ReferenceService, "ReferenceService", "Service Identifier of a prior envelope", false, true, 0, 10, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ReferenceDate, "ReferenceDate", "Date of a prior envelope", false, true, 8, 8, Exiv2::date, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ReferenceNumber, "ReferenceNumber", "Envelope Number of a prior envelope", false, true, 8, 8, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::DateCreated, "DateCreated", "Creation date of intellectual content", false, false, 8, 8, Exiv2::date, IptcDataSets::application2, "Date created"), + DataSet(IptcDataSets::TimeCreated, "TimeCreated", "Creation time of intellectual content", false, false, 11, 11, Exiv2::time, IptcDataSets::application2, ""), + DataSet(IptcDataSets::DigitizationDate, "DigitizationDate", "Creation date of digital representation", false, false, 8, 8, Exiv2::date, IptcDataSets::application2, ""), + DataSet(IptcDataSets::DigitizationTime, "DigitizationTime", "Creation time of digital representation", false, false, 11, 11, Exiv2::time, IptcDataSets::application2, ""), + DataSet(IptcDataSets::Program, "Program", "Content creation program", false, false, 0, 32, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ProgramVersion, "ProgramVersion", "Content creation program version", false, false, 0, 10, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ObjectCycle, "ObjectCycle", "Morning, evening, or both", false, false, 1, 1, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::Byline, "Byline", "Name of content creator", false, true, 0, 32, Exiv2::string, IptcDataSets::application2, "Author"), + DataSet(IptcDataSets::BylineTitle, "BylineTitle", "Title of content creator", false, true, 0, 32, Exiv2::string, IptcDataSets::application2, "Authors Position"), + DataSet(IptcDataSets::City, "City", "City of content origin", false, false, 0, 32, Exiv2::string, IptcDataSets::application2, "City"), + DataSet(IptcDataSets::SubLocation, "SubLocation", "Location within city", false, false, 0, 32, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ProvinceState, "ProvinceState", "Province/State of content origin", false, false, 0, 32, Exiv2::string, IptcDataSets::application2, "State/Province"), + DataSet(IptcDataSets::CountryCode, "CountryCode", "ISO country code of content origin", false, false, 3, 3, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::CountryName, "CountryName", "Full country name of content origin", false, false, 0, 64, Exiv2::string, IptcDataSets::application2, "Country"), + DataSet(IptcDataSets::TransmissionReference, "TransmissionReference", "Location of original transmission", false, false, 0, 32, Exiv2::string, IptcDataSets::application2, "Transmission Reference"), + DataSet(IptcDataSets::Headline, "Headline", "Content synopsis", false, false, 0, 256, Exiv2::string, IptcDataSets::application2, "Headline"), + DataSet(IptcDataSets::Credit, "Credit", "Content provider", false, false, 0, 32, Exiv2::string, IptcDataSets::application2, "Credit"), + DataSet(IptcDataSets::Source, "Source", "Original owner of content", false, false, 0, 32, Exiv2::string, IptcDataSets::application2, "Source"), + DataSet(IptcDataSets::Copyright, "Copyright", "Necessary copyright notice", false, false, 0, 128, Exiv2::string, IptcDataSets::application2, "Copyright notice"), + DataSet(IptcDataSets::Contact, "Contact", "Person or organisation to contact", false, true, 0, 128, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::Caption, "Caption", "Content description", false, false, 0, 2000, Exiv2::string, IptcDataSets::application2, "Description"), + DataSet(IptcDataSets::Writer, "Writer", "Person responsible for caption", false, true, 0, 32, Exiv2::string, IptcDataSets::application2, "Description writer"), + DataSet(IptcDataSets::RasterizedCaption, "RasterizedCaption", "Black and white caption image", false, false, 7360, 7360, Exiv2::undefined, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ImageType, "ImageType", "Color components in an image", false, false, 2, 2, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::ImageOrientation, "ImageOrientation", "Indicates the layout of an image", false, false, 1, 1, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::Language, "Language", "ISO 639:1988 language code", false, false, 2, 3, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::AudioType, "AudioType", "Information about audio content", false, false, 2, 2, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::AudioRate, "AudioRate", "Sampling rate of audio content", false, false, 6, 6, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::AudioResolution, "AudioResolution", "Sampling resolution of audio content", false, false, 2, 2, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::AudioDuration, "AudioDuration", "Duration of audio content", false, false, 6, 6, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::AudioOutcue, "AudioOutcue", "Final words or sounds of audio content", false, false, 0, 64, Exiv2::string, IptcDataSets::application2, ""), + DataSet(IptcDataSets::PreviewFormat, "PreviewFormat", "IIM appendix A file format of preview", false, false, 2, 2, Exiv2::unsignedShort, IptcDataSets::application2, ""), + DataSet(IptcDataSets::PreviewVersion, "PreviewVersion", "File format version of preview", false, false, 2, 2, Exiv2::unsignedShort, IptcDataSets::application2, ""), + DataSet(IptcDataSets::Preview, "Preview", "Binary preview data", false, false, 0, 256000, Exiv2::undefined, IptcDataSets::application2, ""), + DataSet(0xffff, "(Invalid)", "(Invalid)", false, false, 0, 0, Exiv2::unsignedShort, IptcDataSets::application2, "") + }; + + static const DataSet unknownDataSet(0xffff, "Unknown dataset", "Unknown dataset", false, true, 0, 0xffffffff, Exiv2::string, IptcDataSets::invalidRecord, "Unknown dataset"); + + // Dataset lookup lists.This is an array with pointers to one list per IIM4 Record. + // The record id is used as the index into the array. + const DataSet* IptcDataSets::records_[] = { + 0, + envelopeRecord, application2Record, + 0 + }; + + int IptcDataSets::dataSetIdx(uint16_t number, uint16_t recordId) + { + if( recordId != envelope && recordId != application2 ) return -1; + const DataSet* dataSet = records_[recordId]; + if (dataSet == 0) return -1; + int idx; + for (idx = 0; dataSet[idx].number_ != number; ++idx) { + if (dataSet[idx].number_ == 0xffff) return -1; + } + return idx; + } + + int IptcDataSets::dataSetIdx(const std::string& dataSetName, uint16_t recordId) + { + if( recordId != envelope && recordId != application2 ) return -1; + const DataSet* dataSet = records_[recordId]; + if (dataSet == 0) return -1; + int idx; + for (idx = 0; dataSet[idx].name_ != dataSetName; ++idx) { + if (dataSet[idx].number_ == 0xffff) return -1; + } + return idx; + } + + TypeId IptcDataSets::dataSetType(uint16_t number, uint16_t recordId) + { + int idx = dataSetIdx(number, recordId); + if (idx == -1) return unknownDataSet.type_; + return records_[recordId][idx].type_; + } + + std::string IptcDataSets::dataSetName(uint16_t number, uint16_t recordId) + { + int idx = dataSetIdx(number, recordId); + if (idx != -1) return records_[recordId][idx].name_; + + std::ostringstream os; + os << "0x" << std::setw(4) << std::setfill('0') << std::right + << std::hex << number; + return os.str(); + } + + const char* IptcDataSets::dataSetDesc(uint16_t number, uint16_t recordId) + { + int idx = dataSetIdx(number, recordId); + if (idx == -1) return unknownDataSet.desc_; + return records_[recordId][idx].desc_; + } + + const char* IptcDataSets::dataSetPsName(uint16_t number, uint16_t recordId) + { + int idx = dataSetIdx(number, recordId); + if (idx == -1) return unknownDataSet.photoshop_; + return records_[recordId][idx].photoshop_; + } + + bool IptcDataSets::dataSetRepeatable(uint16_t number, uint16_t recordId) + { + int idx = dataSetIdx(number, recordId); + if (idx == -1) return unknownDataSet.repeatable_; + return records_[recordId][idx].repeatable_; + } + + uint16_t IptcDataSets::dataSet(const std::string& dataSetName, + uint16_t recordId) + { + uint16_t dataSet; + int idx = dataSetIdx(dataSetName, recordId); + if (idx != -1) { + // dataSetIdx checks the range of recordId + dataSet = records_[recordId][idx].number_; + } + else { + if (!isHex(dataSetName, 4, "0x")) throw Error(4, dataSetName); + std::istringstream is(dataSetName); + is >> std::hex >> dataSet; + } + return dataSet; + } + + std::string IptcDataSets::recordName(uint16_t recordId) + { + if (recordId == envelope || recordId == application2) { + return recordInfo_[recordId].name_; + } + + std::ostringstream os; + os << "0x" << std::setw(4) << std::setfill('0') << std::right + << std::hex << recordId; + return os.str(); + } + + const char* IptcDataSets::recordDesc(uint16_t recordId) + { + if (recordId != envelope && recordId != application2) { + return unknownDataSet.desc_; + } + return recordInfo_[recordId].desc_; + } + + uint16_t IptcDataSets::recordId(const std::string& recordName) + { + uint16_t i; + for (i = application2; i > 0; --i) { + if (recordInfo_[i].name_ == recordName) break; + } + if (i == 0) { + if (!isHex(recordName, 4, "0x")) throw Error(5, recordName); + std::istringstream is(recordName); + is >> std::hex >> i; + } + return i; + } + + void IptcDataSets::dataSetList(std::ostream& os) + { + const int count = sizeof(records_)/sizeof(records_[0]); + for (int i=0; i < count; ++i) { + const DataSet *record = records_[i]; + for (int j=0; record != 0 && record[j].number_ != 0xffff; ++j) { + os << record[j] << "\n"; + } + } + } // IptcDataSets::dataSetList + + const char* IptcKey::familyName_ = "Iptc"; + + IptcKey::IptcKey(const std::string& key) + : key_(key) + { + decomposeKey(); + } + + IptcKey::IptcKey(uint16_t tag, uint16_t record) + : tag_(tag), record_(record) + { + makeKey(); + } + + IptcKey::IptcKey(const IptcKey& rhs) + : tag_(rhs.tag_), record_(rhs.record_), key_(rhs.key_) + { + } + + IptcKey& IptcKey::operator=(const IptcKey& rhs) + { + if (this == &rhs) return *this; + Key::operator=(rhs); + tag_ = rhs.tag_; + record_ = rhs.record_; + key_ = rhs.key_; + return *this; + } + + IptcKey::AutoPtr IptcKey::clone() const + { + return AutoPtr(clone_()); + } + + IptcKey* IptcKey::clone_() const + { + return new IptcKey(*this); + } + + void IptcKey::decomposeKey() + { + // Get the family name, record name and dataSet 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 recordName = key_.substr(pos0, pos1 - pos0); + if (recordName == "") throw Error(6, key_); + std::string dataSetName = key_.substr(pos1 + 1); + if (dataSetName == "") throw Error(6, key_); + + // Use the parts of the key to find dataSet and recordId + uint16_t recId = IptcDataSets::recordId(recordName); + uint16_t dataSet = IptcDataSets::dataSet(dataSetName, recId); + + // Possibly translate hex name parts (0xabcd) to real names + recordName = IptcDataSets::recordName(recId); + dataSetName = IptcDataSets::dataSetName(dataSet, recId); + + tag_ = dataSet; + record_ = recId; + key_ = familyName + "." + recordName + "." + dataSetName; + } // IptcKey::decomposeKey + + void IptcKey::makeKey() + { + key_ = std::string(familyName_) + + "." + IptcDataSets::recordName(record_) + + "." + IptcDataSets::dataSetName(tag_, record_); + } + + // ************************************************************************* + // free functions + + std::ostream& operator<<(std::ostream& os, const DataSet& dataSet) + { + IptcKey iptcKey(dataSet.number_, dataSet.recordId_); + return os << dataSet.name_ << ", " + << std::dec << dataSet.number_ << ", " + << "0x" << std::setw(4) << std::setfill('0') + << std::right << std::hex << dataSet.number_ << ", " + << IptcDataSets::recordName(dataSet.recordId_) << ", " + << std::boolalpha << dataSet.mandatory_ << ", " + << dataSet.repeatable_ << ", " + << std::dec << dataSet.minbytes_ << ", " + << dataSet.maxbytes_ << ", " + << iptcKey.key() << ", " + << TypeInfo::typeName( + IptcDataSets::dataSetType(dataSet.number_, + dataSet.recordId_)) << ", " + << dataSet.desc_; + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/datasets.hpp b/src/plugins/exiv2/datasets.hpp @@ -0,0 +1,358 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file datasets.hpp + @brief Iptc dataSet and type information + @version $Rev: 560 $ + @author Brad Schick (brad) <brad@robotbattle.com> + @date 24-Jul-04, brad: created + */ +#ifndef DATASETS_HPP_ +#define DATASETS_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "metadatum.hpp" + +// + standard includes +#include <string> +#include <utility> // for std::pair +#include <iosfwd> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class definitions + + //! Contains information about one record + struct RecordInfo { + //! Constructor + RecordInfo(uint16_t recordId, const char* name, const char* desc); + uint16_t recordId_; //!< Record id + const char* name_; //!< Record name (one word) + const char* desc_; //!< Record description + }; + + //! Dataset information + struct DataSet { + //! Constructor + DataSet( + uint16_t number, + const char* name, + const char* desc, + bool mandatory, + bool repeatable, + uint32_t minbytes, + uint32_t maxbytes, + TypeId type, + uint16_t recordId, + const char* photoshop + ); + uint16_t number_; //!< Dataset number + const char* name_; //!< Dataset name + const char* desc_; //!< Dataset description + bool mandatory_; //!< True if dataset is mandatory + bool repeatable_; //!< True if dataset is repeatable + uint32_t minbytes_; //!< Minimum number of bytes + uint32_t maxbytes_; //!< Maximum number of bytes + TypeId type_; //!< Exiv2 default type + uint16_t recordId_; //!< Record id + const char* photoshop_; //!< Photoshop string + }; // struct DataSet + + //! Container for Iptc dataset information. Implemented as a static class. + class IptcDataSets { + public: + /*! + @name Record identifiers + @brief Record identifiers to logically group dataSets. There are other + possible record types, but they are not standardized by the Iptc + IIM4 standard (and not commonly used in images). + */ + //@{ + static const uint16_t invalidRecord = 0; + static const uint16_t envelope = 1; + static const uint16_t application2 = 2; + //@} + + //! @name Dataset identifiers + //@{ + static const uint16_t ModelVersion = 0; + static const uint16_t Destination = 5; + static const uint16_t FileFormat = 20; + static const uint16_t FileVersion = 22; + static const uint16_t ServiceId = 30; + static const uint16_t EnvelopeNumber = 40; + static const uint16_t ProductId = 50; + static const uint16_t EnvelopePriority = 60; + static const uint16_t DateSent = 70; + static const uint16_t TimeSent = 80; + static const uint16_t CharacterSet = 90; + static const uint16_t UNO = 100; + static const uint16_t ARMId = 120; + static const uint16_t ARMVersion = 122; + static const uint16_t RecordVersion = 0; + static const uint16_t ObjectType = 3; + static const uint16_t ObjectAttribute = 4; + static const uint16_t ObjectName = 5; + static const uint16_t EditStatus = 7; + static const uint16_t EditorialUpdate = 8; + static const uint16_t Urgency = 10; + static const uint16_t Subject = 12; + static const uint16_t Category = 15; + static const uint16_t SuppCategory = 20; + static const uint16_t FixtureId = 22; + static const uint16_t Keywords = 25; + static const uint16_t LocationCode = 26; + static const uint16_t LocationName = 27; + static const uint16_t ReleaseDate = 30; + static const uint16_t ReleaseTime = 35; + static const uint16_t ExpirationDate = 37; + static const uint16_t ExpirationTime = 38; + static const uint16_t SpecialInstructions = 40; + static const uint16_t ActionAdvised = 42; + static const uint16_t ReferenceService = 45; + static const uint16_t ReferenceDate = 47; + static const uint16_t ReferenceNumber = 50; + static const uint16_t DateCreated = 55; + static const uint16_t TimeCreated = 60; + static const uint16_t DigitizationDate = 62; + static const uint16_t DigitizationTime = 63; + static const uint16_t Program = 65; + static const uint16_t ProgramVersion = 70; + static const uint16_t ObjectCycle = 75; + static const uint16_t Byline = 80; + static const uint16_t BylineTitle = 85; + static const uint16_t City = 90; + static const uint16_t SubLocation = 92; + static const uint16_t ProvinceState = 95; + static const uint16_t CountryCode = 100; + static const uint16_t CountryName = 101; + static const uint16_t TransmissionReference = 103; + static const uint16_t Headline = 105; + static const uint16_t Credit = 110; + static const uint16_t Source = 115; + static const uint16_t Copyright = 116; + static const uint16_t Contact = 118; + static const uint16_t Caption = 120; + static const uint16_t Writer = 122; + static const uint16_t RasterizedCaption = 125; + static const uint16_t ImageType = 130; + static const uint16_t ImageOrientation = 131; + static const uint16_t Language = 135; + static const uint16_t AudioType = 150; + static const uint16_t AudioRate = 151; + static const uint16_t AudioResolution = 152; + static const uint16_t AudioDuration = 153; + static const uint16_t AudioOutcue = 154; + static const uint16_t PreviewFormat = 200; + static const uint16_t PreviewVersion = 201; + static const uint16_t Preview = 202; + //@} + + private: + //! Prevent construction: not implemented. + IptcDataSets() {} + //! Prevent copy-construction: not implemented. + IptcDataSets(const IptcDataSets& rhs); + //! Prevent assignment: not implemented. + IptcDataSets& operator=(const IptcDataSets& rhs); + + public: + /*! + @brief Return the name of the dataset. + @param number The dataset number + @param recordId The Iptc record Id + @return The name of the dataset or a string containing the hexadecimal + value of the dataset in the form "0x01ff", if this is an unknown + dataset. + */ + static std::string dataSetName(uint16_t number, uint16_t recordId); + /*! + @brief Return the description of the dataset. + @param number The dataset number + @param recordId The Iptc record Id + @return The description of the dataset + */ + static const char* dataSetDesc(uint16_t number, uint16_t recordId); + /*! + @brief Return the photohsop name of a given dataset. + @param number The dataset number + @param recordId The Iptc record Id + @return The name used by photoshop for a dataset or an empty + string if photoshop does not use the dataset. + */ + static const char* dataSetPsName(uint16_t number, uint16_t recordId); + /*! + @brief Check if a given dataset is repeatable + @param number The dataset number + @param recordId The Iptc record Id + @return true if the given dataset is repeatable otherwise false + */ + static bool dataSetRepeatable(uint16_t number, uint16_t recordId); + /*! + @brief Return the dataSet number for dataset name and record id + + @param dataSetName dataSet name + @param recordId recordId + + @return dataSet number + + @throw Error if the \em dataSetName or \em recordId are invalid + */ + static uint16_t dataSet(const std::string& dataSetName, uint16_t recordId); + //! Return the type for dataSet number and Record id + static TypeId dataSetType(uint16_t number, uint16_t recordId); + /*! + @brief Return the name of the Record + @param recordId The record id + @return The name of the record or a string containing the hexadecimal + value of the record in the form "0x01ff", if this is an + unknown record. + */ + static std::string recordName(uint16_t recordId); + /*! + @brief Return the description of a record + @param recordId Record Id number + @return the description of the Record + */ + static const char* recordDesc(uint16_t recordId); + /*! + @brief Return the Id number of a record + @param recordName Name of a record type + @return the Id number of a Record + @throw Error if the record is not known; + */ + static uint16_t recordId(const std::string& recordName); + //! Print a list of all dataSets to output stream + static void dataSetList(std::ostream& os); + + private: + static int dataSetIdx(uint16_t number, uint16_t recordId); + static int dataSetIdx(const std::string& dataSetName, uint16_t recordId); + + static const DataSet* records_[]; + static const RecordInfo recordInfo_[]; + + }; // class IptcDataSets + + /*! + @brief Concrete keys for Iptc metadata. + */ + class IptcKey : public Key { + public: + //! Shortcut for an %IptcKey auto pointer. + typedef std::auto_ptr<IptcKey> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor to create an Iptc key from a key string. + + @param key The key string. + @throw Error if the first part of the key is not '<b>Iptc</b>' or + the remaining parts of the key cannot be parsed and + converted to a record name and a dataset name. + */ + explicit IptcKey(const std::string& key); + /*! + @brief Constructor to create an Iptc key from dataset and record ids. + @param tag Dataset id + @param record Record id + */ + IptcKey(uint16_t tag, uint16_t record); + //! Copy constructor + IptcKey(const IptcKey& rhs); + //@} + + //! @name Manipulators + //@{ + /*! + @brief Assignment operator. + */ + IptcKey& operator=(const IptcKey& rhs); + //@} + + //! @name Accessors + //@{ + virtual std::string key() const { return key_; } + virtual const char* familyName() const { return familyName_; } + /*! + @brief Return the name of the group (the second part of the key). + For Iptc keys, the group name is the record name. + */ + virtual std::string groupName() const { return recordName(); } + virtual std::string tagName() const + { return IptcDataSets::dataSetName(tag_, record_); } + virtual uint16_t tag() const { return tag_; } + + AutoPtr clone() const; + //! Return the name of the record + std::string recordName() const + { return IptcDataSets::recordName(record_); } + //! Return the record id + uint16_t record() const { return record_; } + //@} + + protected: + //! @name Manipulators + //@{ + /*! + @brief Set the key corresponding to the dataset and record id. + The key is of the form '<b>Iptc</b>.recordName.dataSetName'. + */ + void makeKey(); + /*! + @brief Parse and convert the key string into dataset and record id. + Updates data members if the string can be decomposed, or throws + \em Error. + + @throw Error if the key cannot be decomposed. + */ + void decomposeKey(); + //@} + + private: + //! Internal virtual copy constructor. + virtual IptcKey* clone_() const; + + // DATA + static const char* familyName_; + + uint16_t tag_; //!< Tag value + uint16_t record_; //!< Record value + std::string key_; //!< Key + + }; // class IptcKey + +// ***************************************************************************** +// free functions + + //! Output operator for dataSet + std::ostream& operator<<(std::ostream& os, const DataSet& dataSet); + +} // namespace Exiv2 + +#endif // #ifndef DATASETS_HPP_ diff --git a/src/plugins/exiv2/doxygen.hpp b/src/plugins/exiv2/doxygen.hpp @@ -0,0 +1,133 @@ +// ***************************************************************** -*- C++ -*- +/*! + @file doxygen.hpp + @brief Additional documentation, this file contains no source code + @version $Rev: 581 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 07-Feb-04, ahu: created + */ +/*! + @mainpage Exif and Iptc metadata manipulation library and tools v0.7 + + @section overview Exiv2 Overview + + %Exiv2 comprises of a C++ library and a command line utility to access image + metadata. %Exiv2 is free software. The homepage of %Exiv2 is at + <A HREF="http://home.arcor.de/ahuggel/exiv2">http://home.arcor.de/ahuggel/exiv2</A>. + + The %Exiv2 library provides + - full read and write access to the Exif and Iptc metadata of an image through + %Exiv2 keys and standard C++ iterators + - a smart Iptc implementation that does not effect data that programs like + Photoshop store in the same image segment + - Exif %MakerNote support: + - %MakerNote tags can be accessed just like any other Exif metadata + - a sophisticated write algorithm avoids corrupting the %MakerNote: + <br>&nbsp;&nbsp;1) the %MakerNote is not re-located if possible at all, and + <br>&nbsp;&nbsp;2) %MakerNote %Ifd offsets are re-calculated if the + %MakerNote needs to be moved (for known %Ifd %MakerNotes) + - new camera make/model specific %MakerNotes can be added to the library with + minimum effort in OO-fashion (by subclassing %MakerNote or %IfdMakerNote) + - extract and delete methods for Exif thumbnails (both, Jpeg and Tiff thumbnails) + - set methods for Exif thumbnails (Jpeg only, Tiff thumbnails can be set from + individual tags) + - complete API documentation (by Doxygen) + - generic lower-level classes to access %Ifd (%Image File Directory) data + structures + + @section getting-started Getting started + + <A HREF="getting-started.html">A few pointers</A> to get you started with + the %Exiv2 library without delay. + + @section metadata Supported Exif and MakerNote tags and Iptc datasets + + - <A HREF="tags-exif.html">Standard Exif tags</A> + - <A HREF="tags-canon.html">Canon MakerNote tags</A> + - <A HREF="tags-fujifilm.html">Fujifilm MakerNote tags</A> + - <A HREF="tags-nikon.html">Nikon MakerNote tags</A> + - <A HREF="tags-olympus.html">Olympus MakerNote tags</A> + - <A HREF="tags-panasonic.html">Panasonic MakerNote tags</A> + - <A HREF="tags-sigma.html">Sigma/Foveon MakerNote tags</A> + - <A HREF="tags-sony.html">Sony MakerNote tags</A> + - <A HREF="tags-iptc.html">Iptc datasets</A> + + @section makernote MakerNote Formats and Specifications + + A summary of <A HREF="makernote.html">MakerNote structures</A> with links to + publicly available specifications. + + @section supp Support + + <p>There is now a + <a title="Exiv2 forum" href="http://uk.groups.yahoo.com/group/exiv2">Yahoo! group for Exiv2 help and discussions</a>.</p> + <p><a title="Join the Exiv2 forum" href="http://uk.groups.yahoo.com/group/exiv2/join">Join the group</a> to post and receive messages or use <em><a title="Post to the Exiv2 forum" href="http://uk.groups.yahoo.com/group/exiv2/post">exiv2user/password</a></em> to post anonymously. Bug reports can be submitted directly to the +<a title="Issue tracker" href="http://dev.robotbattle.com/bugs/main_page.php" + onclick="document.images['tracker'].src='http://www.whatcounter.com/dlcount.php?id=ahu&amp;url='+this.href"> +bug tracking system</a>.</p> + + @section devel Development + + <p>%Exiv2 is maintained in a publicly available subversion repository. + There is a <a title="Last 50 commits" href="http://dev.robotbattle.com/cmtinfo_svn.php?r=10"> + live list with the latest commits</a> to the repository, and you can + <a title="Online source code repository" href="http://dev.robotbattle.com/~cvsuser/cgi-bin/ns_viewcvs.cgi/exiv2/trunk/"> + browse the source code</a> online. + </p> + <p>To check-out the current source code from the repository, you need a + <a title="Get Subversion!" + href="http://subversion.tigris.org/project_packages.html">subversion client</a>. + If you use a command line client, change to the directory where you want to keep + the source code and type:</p> + + @verbatim $ svn checkout svn://dev.robotbattle.com/exiv2/trunk . @endverbatim + + <p>To download the test data and test drivers for version 0.7 from + the repository, change to your local exiv2-0.7 directory and use the + following command: + </p> + + @verbatim $ svn export svn://dev.robotbattle.com/exiv2/tags/0.7/test @endverbatim + + <P>If you'd like to contribute code, please <A HREF="mailto:ahuggel@gmx.net">contact me</A>. + + @section license License + + <P>Copyright (C) 2004, 2005 Andreas Huggel <ahuggel@gmx.net></P> + + <P>%Exiv2 is free software; you can redistribute it and/or modify it under the + terms of the <a href="gpl-license.html">GNU General Public License</a> as + published by the Free Software Foundation; either version 2 of the License, or + (at your option) any later version.</P> + + <P>%Exiv2 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.</P> + + <P>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., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA.</P> + +*/ +/*! + @example addmoddel.cpp + Sample usage of high-level metadata operations. + */ +/*! + @example exifprint.cpp + Sample program to print Exif data from an image. + */ +/*! + @example exifcomment.cpp + Sample program showing how to set the Exif comment of an image. + */ +/*! + @example iptcprint.cpp + Sample program to print the Iptc metadata of an image + */ +/*! + @example iptceasy.cpp + The quickest way to access, set or modify Iptc metadata + */ diff --git a/src/plugins/exiv2/error.cpp b/src/plugins/exiv2/error.cpp @@ -0,0 +1,121 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: error.cpp + Version: $Rev: 563 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 02-Apr-05, ahu: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: error.cpp 563 2005-04-21 07:21:53Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "error.hpp" + +// + standard includes +#include <string> + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + const ErrMsg Error::errMsg_[] = { + ErrMsg( -1, "Error %0: arg1=%1, arg2=%2, arg3=%3."), + ErrMsg( 0, "Success"), + ErrMsg( 1, "%1"), // %1=error message + + ErrMsg( 2, "%1: %2 (%3)"), // %1=path, %2=strerror, %3=function that failed + // ErrMsg( 3, ""), + + ErrMsg( 4, "Invalid dataset name `%1'"), // %1=dataset name + ErrMsg( 5, "Invalid record name `%1'"), // %1=record name + ErrMsg( 6, "Invalid key `%1'"), // %1=key + ErrMsg( 7, "Invalid tag name or ifdId `%1', ifdId %2"), // %1=tag name, %2=ifdId + ErrMsg( 8, "Value not set"), + ErrMsg( 9, "%1: Failed to open the data source: %2"), // %1=path, %2=strerror + ErrMsg( 10, "%1: Failed to open file (%2): %3"), // %1=path, %2=mode, %3=strerror + ErrMsg( 11, "%1: The file contains data of an unknown image type"), // %1=path + ErrMsg( 12, "The memory contains data of an unknown image type"), + ErrMsg( 13, "Image type %1 is not supported"), // %1=image type + ErrMsg( 14, "Failed to read image data"), + ErrMsg( 15, "This does not look like a JPEG image"), + ErrMsg( 16, "MakerTagInfo registry full"), + ErrMsg( 17, "%1: Failed to rename file to %2: %3"), // %1=old path, %2=new path, %3=strerror + ErrMsg( 18, "%1: Transfer failed: %2"), // %1=path, %2=strerror + ErrMsg( 19, "Memory transfer failed: %1"), // %1=strerror + ErrMsg( 20, "Failed to read input data"), + ErrMsg( 21, "Failed to write image"), + ErrMsg( 22, "Input data does not contain a valid image"), + ErrMsg( 23, "Failed to create Makernote for ifdId %1"), // %1=ifdId + ErrMsg( 24, "Entry::setValue: Value too large (tag=%1, size=%2, requested=%3)"), // %1=tag, %2=dataSize, %3=required size + ErrMsg( 25, "Entry::setDataArea: Value too large (tag=%1, size=%2, requested=%3)"), // %1=tag, %2=dataAreaSize, %3=required size + ErrMsg( 26, "Offset out of range"), + ErrMsg( 27, "Unsupported data area offset type"), + ErrMsg( 28, "Invalid charset: `%1'"), // %1=charset name + ErrMsg( 29, "Unsupported date format"), + ErrMsg( 30, "Unsupported time format"), + + // Last error message (message is not used) + ErrMsg( -2, "(Unknown Error)") + }; + + int Error::errorIdx(int code) + { + int idx; + for (idx = 0; errMsg_[idx].code_ != code; ++idx) { + if (errMsg_[idx].code_ == -2) return 0; + } + return idx; + } + + std::string Error::what() const + { + int idx = errorIdx(code_); + std::string msg = std::string(errMsg_[idx].message_); + std::string::size_type pos; + pos = msg.find("%0"); + if (pos != std::string::npos) { + msg.replace(pos, 2, toString(code_)); + } + if (count_ > 0) { + pos = msg.find("%1"); + if (pos != std::string::npos) { + msg.replace(pos, 2, arg1_); + } + } + if (count_ > 1) { + pos = msg.find("%2"); + if (pos != std::string::npos) { + msg.replace(pos, 2, arg2_); + } + } + if (count_ > 2) { + pos = msg.find("%3"); + if (pos != std::string::npos) { + msg.replace(pos, 2, arg3_); + } + } + return msg; + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/error.hpp b/src/plugins/exiv2/error.hpp @@ -0,0 +1,148 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file error.hpp + @brief Error class for exceptions + @version $Rev: 560 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 15-Jan-04, ahu: created<BR> + 11-Feb-04, ahu: isolated as a component + */ +#ifndef ERROR_HPP_ +#define ERROR_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" + +// + standard includes +#include <string> +#include <iosfwd> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class definitions + + //! Helper structure defining an error message + struct ErrMsg { + //! Constructor + ErrMsg(int code, const char* message) + : code_(code), message_(message) + { + } + int code_; //!< Error code + const char* message_; //!< Error message + }; + + /*! + @brief Error class interface. Allows the definition and use of a hierarchy + of error classes which can all be handled in one catch block. + */ + class AnyError { + public: + //! @name Creators + //@{ + //! Virtual destructor. + virtual ~AnyError() + { + } + //@} + + //! @name Accessors + //@{ + //! Return the error code. + virtual int code() const =0; + /*! + @brief Return the error message. Consider using the output operator + operator<<(std::ostream &os, const AnyError& error) instead. + @note Unlike std::exception::what(), this function returns an + std::string. + */ + virtual std::string what() const =0; + }; // AnyError + + //! %AnyBase output operator + inline std::ostream& operator<<(std::ostream& os, const AnyError& error) + { + return os << error.what(); + } + + /*! + @brief Simple error class used for exceptions. An output operator is + provided to print errors to a stream. + */ + class Error : public AnyError { + public: + //! @name Creators + //@{ + //! Constructor taking only an error code + explicit Error(int code) + : code_(code), count_(0) + { + } + //! Constructor taking an error code and one argument + template<typename A> + Error(int code, const A& arg1) + : code_(code), count_(1), arg1_(toString(arg1)) + { + } + //! Constructor taking an error code and two arguments + template<typename A, typename B> + Error(int code, const A& arg1, const B& arg2) + : code_(code), count_(2), + arg1_(toString(arg1)), arg2_(toString(arg2)) + { + } + //! Constructor taking an error code and three arguments + template<typename A, typename B, typename C> + Error(int code, const A& arg1, const B& arg2, const C& arg3) + : code_(code), count_(3), + arg1_(toString(arg1)), arg2_(toString(arg2)), arg3_(toString(arg3)) + { + } + //@} + + //! @name Accessors + //@{ + virtual int code() const { return code_; } + virtual std::string what() const; + //@} + + private: + static int errorIdx(int code); + + // DATA + int code_; //!< Error code + int count_; //!< Number of arguments + std::string arg1_; //!< First argument + std::string arg2_; //!< Second argument + std::string arg3_; //!< Third argument + + static const ErrMsg errMsg_[]; //!< List of error messages + }; // class Error + +} // namespace Exiv2 + +#endif // #ifndef ERROR_HPP_ diff --git a/src/plugins/exiv2/exif.cpp b/src/plugins/exiv2/exif.cpp @@ -0,0 +1,1230 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: exif.cpp + Version: $Rev: 569 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 26-Jan-04, ahu: created + 11-Feb-04, ahu: isolated as a component + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: exif.cpp 569 2005-05-28 05:48:43Z ahuggel $"); + +// Define DEBUG_MAKERNOTE to output debug information to std::cerr, e.g, by +// calling make like this: make DEFS=-DDEBUG_MAKERNOTE exif.o +//#define DEBUG_MAKERNOTE + +// ***************************************************************************** +// included header files +#ifdef _MSC_VER +# include "exv_msvc.h" +#else +# include "exv_conf.h" +#endif + +#include "exif.hpp" +#include "types.hpp" +#include "basicio.hpp" +#include "error.hpp" +#include "value.hpp" +#include "ifd.hpp" +#include "tags.hpp" +#include "jpgimage.hpp" +#include "makernote.hpp" +#include "futils.hpp" + +// + standard includes +#include <iostream> +#include <sstream> +#include <utility> +#include <algorithm> +#include <map> +#include <cstring> +#include <cassert> +#include <cstdio> +#include <sys/types.h> // for stat() +#include <sys/stat.h> // for stat() +#ifdef EXV_HAVE_UNISTD_H +# include <unistd.h> // for stat() +#endif + +// ***************************************************************************** +// local declarations +namespace { + + /* + Set the data of the entry identified by tag in ifd to an unsigned long + with the value of offset. If no entry with this tag exists in ifd, an + entry of type unsigned long with one component is created. + */ + void setOffsetTag(Exiv2::Ifd& ifd, + int idx, + uint16_t tag, + uint32_t offset, + Exiv2::ByteOrder byteOrder); + + // Read file path into a DataBuf, which is returned. + Exiv2::DataBuf readFile(const std::string& path); + +} + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + Exifdatum::Exifdatum(const Entry& e, ByteOrder byteOrder) + : key_(ExifKey::AutoPtr(new ExifKey(e))) + { + setValue(e, byteOrder); + } + + Exifdatum::Exifdatum(const ExifKey& key, const Value* pValue) + : key_(key.clone()) + { + if (pValue) value_ = pValue->clone(); + } + + Exifdatum::~Exifdatum() + { + } + + Exifdatum::Exifdatum(const Exifdatum& rhs) + : Metadatum(rhs) + { + if (rhs.key_.get() != 0) key_ = rhs.key_->clone(); // deep copy + if (rhs.value_.get() != 0) value_ = rhs.value_->clone(); // deep copy + } + + Exifdatum& Exifdatum::operator=(const Exifdatum& rhs) + { + if (this == &rhs) return *this; + Metadatum::operator=(rhs); + + 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; + } // Exifdatum::operator= + + Exifdatum& Exifdatum::operator=(const std::string& value) + { + setValue(value); + return *this; + } + + Exifdatum& Exifdatum::operator=(const uint16_t& value) + { + return Exiv2::setValue(*this, value); + } + + Exifdatum& Exifdatum::operator=(const uint32_t& value) + { + return Exiv2::setValue(*this, value); + } + + Exifdatum& Exifdatum::operator=(const URational& value) + { + return Exiv2::setValue(*this, value); + } + + Exifdatum& Exifdatum::operator=(const int16_t& value) + { + return Exiv2::setValue(*this, value); + } + + Exifdatum& Exifdatum::operator=(const int32_t& value) + { + return Exiv2::setValue(*this, value); + } + + Exifdatum& Exifdatum::operator=(const Rational& value) + { + return Exiv2::setValue(*this, value); + } + + Exifdatum& Exifdatum::operator=(const Value& value) + { + setValue(&value); + return *this; + } + + void Exifdatum::setValue(const Value* pValue) + { + value_.reset(); + if (pValue) value_ = pValue->clone(); + } + + void Exifdatum::setValue(const Entry& e, ByteOrder byteOrder) + { + value_ = Value::create(TypeId(e.type())); + value_->read(e.data(), e.count() * e.typeSize(), byteOrder); + value_->setDataArea(e.dataArea(), e.sizeDataArea()); + } + + void Exifdatum::setValue(const std::string& value) + { + if (value_.get() == 0) { + TypeId type = ExifTags::tagType(tag(), ifdId()); + value_ = Value::create(type); + } + value_->read(value); + } + + int TiffThumbnail::setDataArea(ExifData& exifData, Ifd* pIfd1, + const byte* buf, long len) const + { + // Create a DataBuf that can hold all strips + ExifData::const_iterator sizes; + ExifKey key("Exif.Thumbnail.StripByteCounts"); + sizes = exifData.findKey(key); + if (sizes == exifData.end()) return 2; + + long totalSize = 0; + for (long i = 0; i < sizes->count(); ++i) { + totalSize += sizes->toLong(i); + } + DataBuf stripsBuf(totalSize); + + // Copy all strips into the data buffer. For each strip remember its + // offset from the start of the data buffer + ExifData::iterator stripOffsets; + key = ExifKey("Exif.Thumbnail.StripOffsets"); + stripOffsets = exifData.findKey(key); + if (stripOffsets == exifData.end()) return 2; + if (stripOffsets->count() != sizes->count()) return 2; + + std::ostringstream os; // for the strip offsets + long currentOffset = 0; + long firstOffset = stripOffsets->toLong(0); + long lastOffset = 0; + long lastSize = 0; + for (long i = 0; i < stripOffsets->count(); ++i) { + long offset = stripOffsets->toLong(i); + lastOffset = offset; + long size = sizes->toLong(i); + lastSize = size; + if (len < offset + size) return 1; + + memcpy(stripsBuf.pData_ + currentOffset, buf + offset, size); + os << currentOffset << " "; + currentOffset += size; + } + + // Set StripOffsets data area and relative offsets + stripOffsets->setDataArea(stripsBuf.pData_, stripsBuf.size_); + stripOffsets->setValue(os.str()); + + // Set corresponding data area at IFD1, if it is a contiguous area + if (pIfd1 && firstOffset + totalSize == lastOffset + lastSize) { + Ifd::iterator pos = pIfd1->findTag(0x0111); + assert(pos != pIfd1->end()); + pos->setDataArea(buf + firstOffset, totalSize); + } + + return 0; + } // TiffThumbnail::read + + const char* TiffThumbnail::format() const + { + return "TIFF"; + } + + const char* TiffThumbnail::extension() const + { + return ".tif"; + } + + DataBuf TiffThumbnail::copy(const ExifData& exifData) const + { + // Create a TIFF header and IFD1 + TiffHeader tiffHeader(exifData.byteOrder()); + Ifd ifd1(ifd1Id); + + // Populate IFD (without Exif and GPS tags) from metadata + addToIfd(ifd1, exifData.begin(), exifData.end(), exifData.byteOrder()); + ifd1.erase(0x8769); + ifd1.erase(0x8825); + ifd1.sortByTag(); + + long size = tiffHeader.size() + ifd1.size() + ifd1.dataSize(); + DataBuf buf(size); + long len = tiffHeader.copy(buf.pData_); + len += ifd1.copy(buf.pData_ + len, exifData.byteOrder(), len); + assert(len == size); + return buf; + } + + int JpegThumbnail::setDataArea(ExifData& exifData, Ifd* pIfd1, + const byte* buf, long len) const + { + ExifKey key("Exif.Thumbnail.JPEGInterchangeFormat"); + ExifData::iterator format = exifData.findKey(key); + if (format == exifData.end()) return 1; + long offset = format->toLong(); + key = ExifKey("Exif.Thumbnail.JPEGInterchangeFormatLength"); + ExifData::const_iterator length = exifData.findKey(key); + if (length == exifData.end()) return 1; + long size = length->toLong(); + if (len < offset + size) return 2; + format->setDataArea(buf + offset, size); + format->setValue("0"); + if (pIfd1) { + Ifd::iterator pos = pIfd1->findTag(0x0201); + assert(pos != pIfd1->end()); + pos->setDataArea(buf + offset, size); + } + return 0; + } // JpegThumbnail::setDataArea + + const char* JpegThumbnail::format() const + { + return "JPEG"; + } + + const char* JpegThumbnail::extension() const + { + return ".jpg"; + } + + DataBuf JpegThumbnail::copy(const ExifData& exifData) const + { + ExifKey key("Exif.Thumbnail.JPEGInterchangeFormat"); + ExifData::const_iterator format = exifData.findKey(key); + if (format == exifData.end()) return DataBuf(); + return format->dataArea(); + } + + ExifData::ExifData() + : pTiffHeader_(0), + pIfd0_(0), pExifIfd_(0), pIopIfd_(0), pGpsIfd_(0), pIfd1_(0), + pMakerNote_(0), size_(0), pData_(0), compatible_(true) + { + } + + ExifData::ExifData(const ExifData& rhs) + : exifMetadata_(rhs.exifMetadata_), pTiffHeader_(0), + pIfd0_(0), pExifIfd_(0), pIopIfd_(0), pGpsIfd_(0), pIfd1_(0), + pMakerNote_(0), size_(0), pData_(0), compatible_(rhs.compatible_) + { + pData_ = new byte[rhs.size_]; + size_ = rhs.size_; + memcpy(pData_, rhs.pData_, rhs.size_); + + if (rhs.pTiffHeader_) { + pTiffHeader_ = new TiffHeader(*rhs.pTiffHeader_); + } + if (rhs.pIfd0_) { + pIfd0_ = new Ifd(*rhs.pIfd0_); + pIfd0_->updateBase(pData_); + } + if (rhs.pExifIfd_) { + pExifIfd_ = new Ifd(*rhs.pExifIfd_); + pExifIfd_->updateBase(pData_); + } + if (rhs.pIopIfd_) { + pIopIfd_ = new Ifd(*rhs.pIopIfd_); + pIopIfd_->updateBase(pData_); + } + if (rhs.pGpsIfd_) { + pGpsIfd_ = new Ifd(*rhs.pGpsIfd_); + pGpsIfd_->updateBase(pData_); + } + if (rhs.pIfd1_) { + pIfd1_ = new Ifd(*rhs.pIfd1_); + pIfd1_->updateBase(pData_); + } + if (rhs.pMakerNote_) { + pMakerNote_ = rhs.pMakerNote_->clone().release(); + pMakerNote_->updateBase(pData_); + } + } + + ExifData::~ExifData() + { + delete pTiffHeader_; + delete pIfd0_; + delete pExifIfd_; + delete pIopIfd_; + delete pGpsIfd_; + delete pIfd1_; + delete pMakerNote_; + delete[] pData_; + } + + ExifData& ExifData::operator=(const ExifData& rhs) + { + if (this == &rhs) return *this; + + exifMetadata_ = rhs.exifMetadata_; + + size_ = 0; + delete[] pData_; + pData_ = new byte[rhs.size_]; + size_ = rhs.size_; + memcpy(pData_, rhs.pData_, rhs.size_); + + delete pTiffHeader_; + pTiffHeader_ = 0; + if (rhs.pTiffHeader_) { + pTiffHeader_ = new TiffHeader(*rhs.pTiffHeader_); + } + delete pIfd0_; + pIfd0_ = 0; + if (rhs.pIfd0_) { + pIfd0_ = new Ifd(*rhs.pIfd0_); + pIfd0_->updateBase(pData_); + } + delete pExifIfd_; + pExifIfd_ = 0; + if (rhs.pExifIfd_) { + pExifIfd_ = new Ifd(*rhs.pExifIfd_); + pExifIfd_->updateBase(pData_); + } + delete pIopIfd_; + pIopIfd_ = 0; + if (rhs.pIopIfd_) { + pIopIfd_ = new Ifd(*rhs.pIopIfd_); + pIopIfd_->updateBase(pData_); + } + delete pGpsIfd_; + pGpsIfd_ = 0; + if (rhs.pGpsIfd_) { + pGpsIfd_ = new Ifd(*rhs.pGpsIfd_); + pGpsIfd_->updateBase(pData_); + } + delete pIfd1_; + pIfd1_ = 0; + if (rhs.pIfd1_) { + pIfd1_ = new Ifd(*rhs.pIfd1_); + pIfd1_->updateBase(pData_); + } + delete pMakerNote_; + pMakerNote_ = 0; + if (rhs.pMakerNote_) { + pMakerNote_ = rhs.pMakerNote_->clone().release(); + pMakerNote_->updateBase(pData_); + } + + compatible_ = rhs.compatible_; + return *this; + } + + Exifdatum& ExifData::operator[](const std::string& key) + { + ExifKey exifKey(key); + iterator pos = findKey(exifKey); + if (pos == end()) { + add(Exifdatum(exifKey)); + pos = findKey(exifKey); + } + return *pos; + } + + int ExifData::load(const byte* buf, long len) + { + // Copy the data buffer + delete[] pData_; + pData_ = new byte[len]; + memcpy(pData_, buf, len); + size_ = len; + + // Read the TIFF header + delete pTiffHeader_; + pTiffHeader_ = new TiffHeader; + assert(pTiffHeader_ != 0); + int rc = pTiffHeader_->read(pData_); + if (rc) return rc; + + // Read IFD0 + delete pIfd0_; + pIfd0_ = new Ifd(ifd0Id, 0, false); + assert(pIfd0_ != 0); + rc = pIfd0_->read(pData_ + pTiffHeader_->offset(), + size_ - pTiffHeader_->offset(), + byteOrder(), + pTiffHeader_->offset()); + if (rc) return rc; + + delete pExifIfd_; + pExifIfd_ = new Ifd(exifIfdId, 0, false); + assert(pExifIfd_ != 0); + // Find and read ExifIFD sub-IFD of IFD0 + rc = pIfd0_->readSubIfd(*pExifIfd_, pData_, size_, byteOrder(), 0x8769); + if (rc) return rc; + // Find MakerNote in ExifIFD, create a MakerNote class + Ifd::iterator pos = pExifIfd_->findTag(0x927c); + Ifd::iterator make = pIfd0_->findTag(0x010f); + Ifd::iterator model = pIfd0_->findTag(0x0110); + if ( pos != pExifIfd_->end() + && make != pIfd0_->end() && model != pIfd0_->end()) { + // Todo: The conversion to string assumes that there is a \0 at the end + // Todo: How to avoid the cast (is that a MSVC thing?) + pMakerNote_ = MakerNoteFactory::create( + reinterpret_cast<const char*>(make->data()), + reinterpret_cast<const char*>(model->data()), + false, + pos->data(), + pos->size(), + byteOrder(), + pExifIfd_->offset() + pos->offset()).release(); + } + // Read the MakerNote + if (pMakerNote_) { + rc = pMakerNote_->read(pos->data(), + pos->size(), + byteOrder(), + pExifIfd_->offset() + pos->offset()); + if (rc) { + // Todo: How to handle debug output like this + std::cerr << "Warning: Failed to read Makernote, rc = " + << rc << "\n"; + delete pMakerNote_; + pMakerNote_ = 0; + } + } + // If we successfully parsed the MakerNote, delete the raw MakerNote, + // the parsed MakerNote is the primary MakerNote from now on + if (pMakerNote_) { + pExifIfd_->erase(pos); + } + + delete pIopIfd_; + pIopIfd_ = new Ifd(iopIfdId, 0, false); + assert(pIopIfd_ != 0); + // Find and read Interoperability IFD in ExifIFD + rc = pExifIfd_->readSubIfd(*pIopIfd_, pData_, size_, byteOrder(), 0xa005); + if (rc) return rc; + + delete pGpsIfd_; + pGpsIfd_ = new Ifd(gpsIfdId, 0, false); + assert(pGpsIfd_ != 0); + // Find and read GPSInfo sub-IFD in IFD0 + rc = pIfd0_->readSubIfd(*pGpsIfd_, pData_, size_, byteOrder(), 0x8825); + if (rc) return rc; + + delete pIfd1_; + pIfd1_ = new Ifd(ifd1Id, 0, false); + assert(pIfd1_ != 0); + // Read IFD1 + if (pIfd0_->next()) { + rc = pIfd1_->read(pData_ + pIfd0_->next(), + size_ - pIfd0_->next(), + byteOrder(), + pIfd0_->next()); + if (rc) return rc; + } + // Find and delete ExifIFD sub-IFD of IFD1 + pos = pIfd1_->findTag(0x8769); + if (pos != pIfd1_->end()) { + pIfd1_->erase(pos); + rc = 7; + } + // Find and delete GPSInfo sub-IFD in IFD1 + pos = pIfd1_->findTag(0x8825); + if (pos != pIfd1_->end()) { + pIfd1_->erase(pos); + rc = 7; + } + // Copy all entries from the IFDs and the MakerNote to the metadata + exifMetadata_.clear(); + add(pIfd0_->begin(), pIfd0_->end(), byteOrder()); + add(pExifIfd_->begin(), pExifIfd_->end(), byteOrder()); + if (pMakerNote_) { + add(pMakerNote_->begin(), pMakerNote_->end(), + (pMakerNote_->byteOrder() == invalidByteOrder ? + byteOrder() : pMakerNote_->byteOrder())); + } + add(pIopIfd_->begin(), pIopIfd_->end(), byteOrder()); + add(pGpsIfd_->begin(), pGpsIfd_->end(), byteOrder()); + add(pIfd1_->begin(), pIfd1_->end(), byteOrder()); + // Read the thumbnail (but don't worry whether it was successful or not) + readThumbnail(); + + return rc; + } // ExifData::load + + + DataBuf ExifData::copy() + { + DataBuf buf; + // If we can update the internal IFDs and the underlying data buffer + // from the metadata without changing the data size, then it is enough + // to copy the data buffer. + if (compatible_ && updateEntries()) { +#ifdef DEBUG_MAKERNOTE + std::cerr << "->>>>>> using non-intrusive writing <<<<<<-\n"; +#endif + buf.alloc(size_); + memcpy(buf.pData_, pData_, size_); + } + // Else we have to do it the hard way... + else { +#ifdef DEBUG_MAKERNOTE + std::cerr << "->>>>>> writing from metadata <<<<<<-\n"; +#endif + buf = copyFromMetadata(); + } + return buf; + } + + DataBuf ExifData::copyFromMetadata() + { + // Build IFD0 + Ifd ifd0(ifd0Id); + addToIfd(ifd0, begin(), end(), byteOrder()); + + // Build Exif IFD from metadata + Ifd exifIfd(exifIfdId); + addToIfd(exifIfd, begin(), end(), byteOrder()); + MakerNote::AutoPtr makerNote; + if (pMakerNote_) { + // Build MakerNote from metadata + makerNote = pMakerNote_->create(); + addToMakerNote(makerNote.get(), + begin(), end(), + (pMakerNote_->byteOrder() == invalidByteOrder ? + byteOrder() : pMakerNote_->byteOrder())); + // Create a placeholder MakerNote entry of the correct size and + // add it to the Exif IFD (because we don't know the offset yet) + Entry e; + e.setIfdId(exifIfd.ifdId()); + e.setTag(0x927c); + DataBuf tmpBuf(makerNote->size()); + memset(tmpBuf.pData_, 0x0, tmpBuf.size_); + e.setValue(undefined, tmpBuf.size_, tmpBuf.pData_, tmpBuf.size_); + exifIfd.erase(0x927c); + exifIfd.add(e); + } + + // Build Interoperability IFD from metadata + Ifd iopIfd(iopIfdId); + addToIfd(iopIfd, begin(), end(), byteOrder()); + + // Build GPSInfo IFD from metadata + Ifd gpsIfd(gpsIfdId); + addToIfd(gpsIfd, begin(), end(), byteOrder()); + + // build IFD1 from metadata + Ifd ifd1(ifd1Id); + addToIfd(ifd1, begin(), end(), byteOrder()); + // Set a temporary dummy offset in IFD0 + if (ifd1.size() > 0) { + ifd0.setNext(1, byteOrder()); + } + + // Compute the new IFD offsets + int exifIdx = ifd0.erase(0x8769); + int gpsIdx = ifd0.erase(0x8825); + int iopIdx = exifIfd.erase(0xa005); + + TiffHeader tiffHeader(byteOrder()); + long ifd0Offset = tiffHeader.size(); + bool addOffsetTag = false; + long exifIfdOffset = ifd0Offset + ifd0.size() + ifd0.dataSize(); + if (exifIfd.size() > 0 || iopIfd.size() > 0) { + exifIfdOffset += 12; + addOffsetTag = true; + } + if (gpsIfd.size() > 0) { + exifIfdOffset += 12; + addOffsetTag = true; + } + if (ifd0.size() == 0 && addOffsetTag) { + exifIfdOffset += 6; + } + addOffsetTag = false; + long iopIfdOffset = exifIfdOffset + exifIfd.size() + exifIfd.dataSize(); + if (iopIfd.size() > 0) { + iopIfdOffset += 12; + addOffsetTag = true; + } + if (exifIfd.size() == 0 && addOffsetTag) { + iopIfdOffset += 6; + } + long gpsIfdOffset = iopIfdOffset + iopIfd.size() + iopIfd.dataSize(); + long ifd1Offset = gpsIfdOffset + gpsIfd.size() + gpsIfd.dataSize(); + + // Set the offset to IFD1 in IFD0 + if (ifd1.size() > 0) { + ifd0.setNext(ifd1Offset, byteOrder()); + } + + // Set the offset to the Exif IFD in IFD0 + if (exifIfd.size() > 0 || iopIfd.size() > 0) { + setOffsetTag(ifd0, exifIdx, 0x8769, exifIfdOffset, byteOrder()); + } + // Set the offset to the GPSInfo IFD in IFD0 + if (gpsIfd.size() > 0) { + setOffsetTag(ifd0, gpsIdx, 0x8825, gpsIfdOffset, byteOrder()); + } + // Set the offset to the Interoperability IFD in Exif IFD + if (iopIfd.size() > 0) { + setOffsetTag(exifIfd, iopIdx, 0xa005, iopIfdOffset, byteOrder()); + } + + // Allocate a data buffer big enough for all metadata + long size = tiffHeader.size(); + size += ifd0.size() + ifd0.dataSize(); + size += exifIfd.size() + exifIfd.dataSize(); + size += iopIfd.size() + iopIfd.dataSize(); + size += gpsIfd.size() + gpsIfd.dataSize(); + size += ifd1.size() + ifd1.dataSize(); + DataBuf buf(size); + + // Copy the TIFF header, all IFDs, MakerNote and thumbnail to the buffer + size = tiffHeader.copy(buf.pData_); + ifd0.sortByTag(); + size += ifd0.copy(buf.pData_ + ifd0Offset, byteOrder(), ifd0Offset); + exifIfd.sortByTag(); + size += exifIfd.copy(buf.pData_ + exifIfdOffset, byteOrder(), exifIfdOffset); + if (makerNote.get() != 0) { + // Copy the MakerNote over the placeholder data + Entries::iterator mn = exifIfd.findTag(0x927c); + // Do _not_ sort the makernote; vendors (at least Canon), don't seem + // to bother about this TIFF standard requirement, so writing the + // makernote as is might result in fewer deviations from the original + makerNote->copy(buf.pData_ + exifIfdOffset + mn->offset(), + byteOrder(), + exifIfdOffset + mn->offset()); + } + iopIfd.sortByTag(); + size += iopIfd.copy(buf.pData_ + iopIfdOffset, byteOrder(), iopIfdOffset); + gpsIfd.sortByTag(); + size += gpsIfd.copy(buf.pData_ + gpsIfdOffset, byteOrder(), gpsIfdOffset); + ifd1.sortByTag(); + size += ifd1.copy(buf.pData_ + ifd1Offset, byteOrder(), ifd1Offset); + assert(size == buf.size_); + return buf; + } // ExifData::copyFromMetadata + + void ExifData::add(Entries::const_iterator begin, + Entries::const_iterator end, + ByteOrder byteOrder) + { + Entries::const_iterator i = begin; + for (; i != end; ++i) { + add(Exifdatum(*i, byteOrder)); + } + } + + void ExifData::add(const ExifKey& key, const Value* pValue) + { + add(Exifdatum(key, pValue)); + } + + void ExifData::add(const Exifdatum& exifdatum) + { + if (ExifTags::isMakerIfd(exifdatum.ifdId())) { + if (pMakerNote_ == 0) { + pMakerNote_ = MakerNoteFactory::create(exifdatum.ifdId()).release(); + } + if (pMakerNote_ == 0) throw Error(23, exifdatum.ifdId()); + } + // allow duplicates + exifMetadata_.push_back(exifdatum); + } + + ExifData::const_iterator ExifData::findKey(const ExifKey& key) const + { + return std::find_if(exifMetadata_.begin(), exifMetadata_.end(), + FindMetadatumByKey(key.key())); + } + + ExifData::iterator ExifData::findKey(const ExifKey& key) + { + return std::find_if(exifMetadata_.begin(), exifMetadata_.end(), + FindMetadatumByKey(key.key())); + } + + ExifData::const_iterator ExifData::findIfdIdIdx(IfdId ifdId, int idx) const + { + return std::find_if(exifMetadata_.begin(), exifMetadata_.end(), + FindMetadatumByIfdIdIdx(ifdId, idx)); + } + + ExifData::iterator ExifData::findIfdIdIdx(IfdId ifdId, int idx) + { + return std::find_if(exifMetadata_.begin(), exifMetadata_.end(), + FindMetadatumByIfdIdIdx(ifdId, idx)); + } + + void ExifData::sortByKey() + { + std::sort(exifMetadata_.begin(), exifMetadata_.end(), cmpMetadataByKey); + } + + void ExifData::sortByTag() + { + std::sort(exifMetadata_.begin(), exifMetadata_.end(), cmpMetadataByTag); + } + + ExifData::iterator ExifData::erase(ExifData::iterator pos) + { + return exifMetadata_.erase(pos); + } + + void ExifData::setJpegThumbnail(const byte* buf, long size) + { + (*this)["Exif.Thumbnail.Compression"] = uint16_t(6); + Exifdatum& format = (*this)["Exif.Thumbnail.JPEGInterchangeFormat"]; + format = uint32_t(0); + format.setDataArea(buf, size); + (*this)["Exif.Thumbnail.JPEGInterchangeFormatLength"] = uint32_t(size); + } + + void ExifData::setJpegThumbnail(const byte* buf, long size, + URational xres, URational yres, uint16_t unit) + { + setJpegThumbnail(buf, size); + (*this)["Exif.Thumbnail.XResolution"] = xres; + (*this)["Exif.Thumbnail.YResolution"] = yres; + (*this)["Exif.Thumbnail.ResolutionUnit"] = unit; + } + + void ExifData::setJpegThumbnail(const std::string& path) + { + DataBuf thumb = readFile(path); // may throw + setJpegThumbnail(thumb.pData_, thumb.size_); + } + + void ExifData::setJpegThumbnail(const std::string& path, + URational xres, URational yres, uint16_t unit) + { + DataBuf thumb = readFile(path); // may throw + setJpegThumbnail(thumb.pData_, thumb.size_, xres, yres, unit); + } + + long ExifData::eraseThumbnail() + { + // First, determine if the thumbnail is at the end of the Exif data + bool stp = stdThumbPosition(); + // Delete all Exif.Thumbnail.* (IFD1) metadata + ExifMetadata::iterator i = begin(); + while (i != end()) { + if (i->ifdId() == ifd1Id) { + i = erase(i); + } + else { + ++i; + } + } + long delta = 0; + if (stp) { + delta = size_; + if (size_ > 0 && pIfd0_ && pIfd0_->next() > 0) { + // Truncate IFD1 and thumbnail data from the data buffer + size_ = pIfd0_->next(); + pIfd0_->setNext(0, byteOrder()); + if (pIfd1_) pIfd1_->clear(); + } + delta -= size_; + } + else { + // We will have to write the hard way and re-arrange the data + compatible_ = false; + if (pIfd1_) delta = pIfd1_->size() + pIfd1_->dataSize(); + } + return delta; + } // ExifData::eraseThumbnail + + bool ExifData::stdThumbPosition() const + { + if ( pIfd0_ == 0 || pExifIfd_ == 0 || pIopIfd_ == 0 + || pGpsIfd_ == 0 || pIfd1_ == 0) return true; + + // Todo: There is still an invalid assumption here: The data of an IFD + // can be stored in multiple non-contiguous blocks. In this case, + // dataOffset + dataSize does not point to the end of the IFD data. + // in particular, this is potentially the case for the remaining Exif + // data in the presence of a known Makernote. + bool rc = true; + Thumbnail::AutoPtr thumbnail = getThumbnail(); + if (thumbnail.get()) { + long maxOffset; + maxOffset = std::max(pIfd0_->offset(), pIfd0_->dataOffset()); + maxOffset = std::max(maxOffset, pExifIfd_->offset()); + maxOffset = std::max(maxOffset, pExifIfd_->dataOffset() + + pExifIfd_->dataSize()); + if (pMakerNote_) { + maxOffset = std::max(maxOffset, pMakerNote_->offset() + + pMakerNote_->size()); + } + maxOffset = std::max(maxOffset, pIopIfd_->offset()); + maxOffset = std::max(maxOffset, pIopIfd_->dataOffset() + + pIopIfd_->dataSize()); + maxOffset = std::max(maxOffset, pGpsIfd_->offset()); + maxOffset = std::max(maxOffset, pGpsIfd_->dataOffset() + + pGpsIfd_->dataSize()); + + if ( maxOffset > pIfd1_->offset() + || maxOffset > pIfd1_->dataOffset() && pIfd1_->dataOffset() > 0) + rc = false; + /* + Todo: Removed condition from the above if(). Should be re-added... + || maxOffset > pThumbnail_->offset() + */ + } + return rc; + } // ExifData::stdThumbPosition + + ByteOrder ExifData::byteOrder() const + { + if (pTiffHeader_) return pTiffHeader_->byteOrder(); + return littleEndian; + } + + int ExifData::writeThumbnail(const std::string& path) const + { + Thumbnail::AutoPtr thumbnail = getThumbnail(); + if (thumbnail.get() == 0) return 8; + + std::string name = path + thumbnail->extension(); + FileIo file(name); + if (file.open("wb") != 0) { + throw Error(10, name, "wb", strError()); + } + + DataBuf buf(thumbnail->copy(*this)); + if (file.write(buf.pData_, buf.size_) != buf.size_) { + throw Error(2, name, strError(), "FileIo::write"); + } + + return 0; + } // ExifData::writeThumbnail + + DataBuf ExifData::copyThumbnail() const + { + Thumbnail::AutoPtr thumbnail = getThumbnail(); + if (thumbnail.get() == 0) return DataBuf(); + return thumbnail->copy(*this); + } + + const char* ExifData::thumbnailFormat() const + { + Thumbnail::AutoPtr thumbnail = getThumbnail(); + if (thumbnail.get() == 0) return ""; + return thumbnail->format(); + } + + const char* ExifData::thumbnailExtension() const + { + Thumbnail::AutoPtr thumbnail = getThumbnail(); + if (thumbnail.get() == 0) return ""; + return thumbnail->extension(); + } + + Thumbnail::AutoPtr ExifData::getThumbnail() const + { + Thumbnail::AutoPtr thumbnail; + const_iterator pos = findKey(ExifKey("Exif.Thumbnail.Compression")); + if (pos != end()) { + long compression = pos->toLong(); + if (compression == 6) { + thumbnail = Thumbnail::AutoPtr(new JpegThumbnail); + } + else { + thumbnail = Thumbnail::AutoPtr(new TiffThumbnail); + } + } + return thumbnail; + + } // ExifData::getThumbnail + + int ExifData::readThumbnail() + { + int rc = -1; + Thumbnail::AutoPtr thumbnail = getThumbnail(); + if (thumbnail.get() != 0) { + rc = thumbnail->setDataArea(*this, pIfd1_, pData_, size_); + } + return rc; + + } // ExifData::readThumbnail + + bool ExifData::updateEntries() + { + if ( pIfd0_ == 0 || pExifIfd_ == 0 || pIopIfd_ == 0 + || pGpsIfd_ == 0 || pIfd1_ == 0) return false; + if (!this->compatible()) return false; + + bool compatible = true; + compatible &= updateRange(pIfd0_->begin(), pIfd0_->end(), byteOrder()); + compatible &= updateRange(pExifIfd_->begin(), pExifIfd_->end(), byteOrder()); + if (pMakerNote_) { + compatible &= updateRange(pMakerNote_->begin(), + pMakerNote_->end(), + (pMakerNote_->byteOrder() == invalidByteOrder ? + byteOrder() : pMakerNote_->byteOrder())); + } + compatible &= updateRange(pIopIfd_->begin(), pIopIfd_->end(), byteOrder()); + compatible &= updateRange(pGpsIfd_->begin(), pGpsIfd_->end(), byteOrder()); + compatible &= updateRange(pIfd1_->begin(), pIfd1_->end(), byteOrder()); + + return compatible; + } // ExifData::updateEntries + + bool ExifData::updateRange(const Entries::iterator& begin, + const Entries::iterator& end, + ByteOrder byteOrder) + { + bool compatible = true; + for (Entries::iterator entry = begin; entry != end; ++entry) { + // find the corresponding Exifdatum + const_iterator md = findIfdIdIdx(entry->ifdId(), entry->idx()); + if (md == this->end()) { + // corresponding Exifdatum was deleted: this is not (yet) a + // supported non-intrusive write operation. + compatible = false; + continue; + } + if (entry->count() == 0 && md->count() == 0) { + // Special case: don't do anything if both the entry and + // Exifdatum have no data. This is to preserve the original + // data in the offset field of an IFD entry with count 0, + // if the Exifdatum was not changed. + } + else if ( entry->size() < md->size() + || entry->sizeDataArea() < md->sizeDataArea()) { + compatible = false; + continue; + } + else { + // Hack: Set the entry's value only if there is no data area. + // This ensures that the original offsets are not overwritten + // with relative offsets from the Exifdatum (which require + // conversion to offsets relative to the start of the TIFF + // header and that is currently only done in intrusive write + // mode). On the other hand, it is thus now not possible to + // change the offsets of an entry with a data area in + // non-intrusive mode. This can be considered a bug. + // Todo: Fix me! + if (md->sizeDataArea() == 0) { + DataBuf buf(md->size()); + md->copy(buf.pData_, byteOrder); + entry->setValue(static_cast<uint16_t>(md->typeId()), + md->count(), + buf.pData_, md->size()); + } + // Always set the data area + DataBuf dataArea(md->dataArea()); + entry->setDataArea(dataArea.pData_, dataArea.size_); + } + } + return compatible; + } // ExifData::updateRange + + bool ExifData::compatible() const + { + bool compatible = true; + // For each Exifdatum, check if it is compatible with the corresponding + // IFD or MakerNote entry + for (const_iterator md = begin(); md != this->end(); ++md) { + std::pair<bool, Entries::const_iterator> rc; + rc = findEntry(md->ifdId(), md->idx()); + // Make sure that we have an entry + if (!rc.first) { + compatible = false; + break; + } + // Make sure that the size of the Exifdatum fits the available size + // of the entry + if ( md->size() > rc.second->size() + || md->sizeDataArea() > rc.second->sizeDataArea()) { + compatible = false; + break; + } + } + return compatible; + } // ExifData::compatible + + std::pair<bool, Entries::const_iterator> + ExifData::findEntry(IfdId ifdId, int idx) const + { + Entries::const_iterator entry; + std::pair<bool, Entries::const_iterator> rc(false, entry); + + if (ExifTags::isMakerIfd(ifdId) && pMakerNote_) { + entry = pMakerNote_->findIdx(idx); + if (entry != pMakerNote_->end()) { + rc.first = true; + rc.second = entry; + } + return rc; + } + const Ifd* ifd = getIfd(ifdId); + if (ifd && isExifIfd(ifdId)) { + entry = ifd->findIdx(idx); + if (entry != ifd->end()) { + rc.first = true; + rc.second = entry; + } + } + return rc; + } // ExifData::findEntry + + const Ifd* ExifData::getIfd(IfdId ifdId) const + { + const Ifd* ifd = 0; + switch (ifdId) { + case ifd0Id: + ifd = pIfd0_; + break; + case exifIfdId: + ifd = pExifIfd_; + break; + case iopIfdId: + ifd = pIopIfd_; + break; + case gpsIfdId: + ifd = pGpsIfd_; + break; + case ifd1Id: + ifd = pIfd1_; + break; + default: + ifd = 0; + break; + } + return ifd; + } // ExifData::getIfd + + // ************************************************************************* + // free functions + + void addToIfd(Ifd& ifd, + ExifMetadata::const_iterator begin, + ExifMetadata::const_iterator end, + ByteOrder byteOrder) + { + for (ExifMetadata::const_iterator i = begin; i != end; ++i) { + // add only metadata with matching IFD id + if (i->ifdId() == ifd.ifdId()) { + addToIfd(ifd, *i, byteOrder); + } + } + } // addToIfd + + void addToIfd(Ifd& ifd, const Exifdatum& md, ByteOrder byteOrder) + { + assert(ifd.alloc()); + + Entry e; + e.setIfdId(md.ifdId()); + e.setIdx(md.idx()); + e.setTag(md.tag()); + e.setOffset(0); // will be calculated when the IFD is written + + DataBuf buf(md.size()); + md.copy(buf.pData_, byteOrder); + e.setValue(static_cast<uint16_t>(md.typeId()), md.count(), + buf.pData_, buf.size_); + + DataBuf dataArea(md.dataArea()); + e.setDataArea(dataArea.pData_, dataArea.size_); + + ifd.add(e); + } // addToIfd + + void addToMakerNote(MakerNote* makerNote, + ExifMetadata::const_iterator begin, + ExifMetadata::const_iterator end, + ByteOrder byteOrder) + { + for (ExifMetadata::const_iterator i = begin; i != end; ++i) { + if (ExifTags::isMakerIfd(i->ifdId())) { + addToMakerNote(makerNote, *i, byteOrder); + } + } + } // addToMakerNote + + void addToMakerNote(MakerNote* makerNote, + const Exifdatum& md, + ByteOrder byteOrder) + { + Entry e; + e.setIfdId(md.ifdId()); + e.setIdx(md.idx()); + e.setTag(md.tag()); + e.setOffset(0); // will be calculated when the makernote is written + + DataBuf buf(md.size()); + md.copy(buf.pData_, byteOrder); + e.setValue(static_cast<uint16_t>(md.typeId()), md.count(), + buf.pData_, md.size()); + + DataBuf dataArea(md.dataArea()); + e.setDataArea(dataArea.pData_, dataArea.size_); + + makerNote->add(e); + } // addToMakerNote + + std::ostream& operator<<(std::ostream& os, const Exifdatum& md) + { + return ExifTags::printTag(os, md.tag(), md.ifdId(), md.value()); + } +} // namespace Exiv2 + +// ***************************************************************************** +// local definitions +namespace { + + void setOffsetTag(Exiv2::Ifd& ifd, + int idx, + uint16_t tag, + uint32_t offset, + Exiv2::ByteOrder byteOrder) + { + Exiv2::Ifd::iterator pos = ifd.findTag(tag); + if (pos == ifd.end()) { + Exiv2::Entry e(ifd.alloc()); + e.setIfdId(ifd.ifdId()); + e.setIdx(idx); + e.setTag(tag); + e.setOffset(0); // will be calculated when the IFD is written + ifd.add(e); + pos = ifd.findTag(tag); + } + pos->setValue(offset, byteOrder); + } + + Exiv2::DataBuf readFile(const std::string& path) + { + Exiv2::FileIo file(path); + if (file.open("rb") != 0) { + throw Exiv2::Error(10, path, "rb", Exiv2::strError()); + } + struct stat st; + if (0 != stat(path.c_str(), &st)) { + throw Exiv2::Error(2, path, Exiv2::strError(), "::stat"); + } + Exiv2::DataBuf buf(st.st_size); + long len = file.read(buf.pData_, buf.size_); + if (len != buf.size_) { + throw Exiv2::Error(2, path, Exiv2::strError(), "FileIo::read"); + } + return buf; + } + +} diff --git a/src/plugins/exiv2/exif.hpp b/src/plugins/exiv2/exif.hpp @@ -0,0 +1,909 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file exif.hpp + @brief Encoding and decoding of Exif data + @version $Rev: 560 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 09-Jan-04, ahu: created + */ +#ifndef EXIF_HPP_ +#define EXIF_HPP_ + +// ***************************************************************************** +// included header files +#include "metadatum.hpp" +#include "types.hpp" +#include "error.hpp" +#include "value.hpp" +#include "ifd.hpp" +#include "tags.hpp" + +// + standard includes +#include <string> +#include <vector> +#include <memory> + +// ***************************************************************************** +// namespace extensions +/*! + @brief Provides classes and functions to encode and decode Exif and Iptc data. + This namespace corresponds to the <b>libexiv2</b> library. + + */ +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class ExifData; + class MakerNote; + class TiffHeader; + +// ***************************************************************************** +// class definitions + + /*! + @brief Information related to one Exif tag. An Exif metadatum consists of + an ExifKey and a Value and provides methods to manipulate these. + */ + class Exifdatum : public Metadatum { + friend std::ostream& operator<<(std::ostream&, const Exifdatum&); + template<typename T> friend Exifdatum& setValue(Exifdatum&, const T&); + public: + //! @name Creators + //@{ + /*! + @brief Constructor for new tags created by an application. The + %Exifdatum is created from a \em key / value pair. %Exifdatum copies + (clones) the \em key and value if one is provided. Alternatively, + a program can create an 'empty' %Exifdatum with only a key + and set the value using setValue(). + + @param key %ExifKey. + @param pValue Pointer to an %Exifdatum value. + @throw Error if the key cannot be parsed and converted. + */ + explicit Exifdatum(const ExifKey& key, const Value* pValue =0); + //! Constructor to build an %Exifdatum from an IFD entry. + Exifdatum(const Entry& e, ByteOrder byteOrder); + //! Copy constructor + Exifdatum(const Exifdatum& rhs); + //! Destructor + virtual ~Exifdatum(); + //@} + + //! @name Manipulators + //@{ + //! Assignment operator + Exifdatum& operator=(const Exifdatum& rhs); + /*! + @brief Assign \em value to the %Exifdatum. The type of the new Value + is set to UShortValue. + */ + Exifdatum& operator=(const uint16_t& value); + /*! + @brief Assign \em value to the %Exifdatum. The type of the new Value + is set to ULongValue. + */ + Exifdatum& operator=(const uint32_t& value); + /*! + @brief Assign \em value to the %Exifdatum. The type of the new Value + is set to URationalValue. + */ + Exifdatum& operator=(const URational& value); + /*! + @brief Assign \em value to the %Exifdatum. The type of the new Value + is set to ShortValue. + */ + Exifdatum& operator=(const int16_t& value); + /*! + @brief Assign \em value to the %Exifdatum. The type of the new Value + is set to LongValue. + */ + Exifdatum& operator=(const int32_t& value); + /*! + @brief Assign \em value to the %Exifdatum. The type of the new Value + is set to RationalValue. + */ + Exifdatum& operator=(const Rational& value); + /*! + @brief Assign \em value to the %Exifdatum. + Calls setValue(const std::string&). + */ + Exifdatum& operator=(const std::string& value); + /*! + @brief Assign \em value to the %Exifdatum. + 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 + std::string&). If the %Exifdatum does not have a Value yet, + then a %Value of the correct type for this %Exifdatum is + created. An AsciiValue is created for unknown tags. + */ + void setValue(const std::string& value); + /*! + @brief Set the value from an IFD entry. + */ + void setValue(const Entry& e, ByteOrder byteOrder); + /*! + @brief Set the data area by copying (cloning) the buffer pointed to + by \em buf. + + Values may have a data area, which can contain additional + information besides the actual value. This method is used to set such + a data area. + + @param buf Pointer to the source data area + @param len Size of the data area + @return Return -1 if the %Exifdatum does not have a value yet or the + value has no data area, else 0. + */ + int setDataArea(const byte* buf, long len) + { return value_.get() == 0 ? -1 : value_->setDataArea(buf, len); } + //@} + + //! @name Accessors + //@{ + //! Return the key of the %Exifdatum. + std::string key() const + { return key_.get() == 0 ? "" : key_->key(); } + //! Return the name of the group (the second part of the key) + std::string groupName() const + { return key_.get() == 0 ? "" : key_->groupName(); } + //! Return the name of the tag (which is also the third part of the key) + std::string tagName() const + { return key_.get() == 0 ? "" : key_->tagName(); } + //! Return the tag + uint16_t tag() const + { return key_.get() == 0 ? 0xffff : key_->tag(); } + //! Return the IFD id + IfdId ifdId() const + { return key_.get() == 0 ? ifdIdNotSet : key_->ifdId(); } + //! Return the name of the IFD + const char* ifdName() const + { return key_.get() == 0 ? "" : key_->ifdName(); } + //! Return the related image item (deprecated) + std::string ifdItem() const + { return key_.get() == 0 ? "" : key_->ifdItem(); } + //! Return the index (unique id of this key within the original IFD) + int idx() const + { return key_.get() == 0 ? 0 : key_->idx(); } + /*! + @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); } + //! 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 <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. + */ + 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 + { if (value_.get() != 0) return *value_; throw Error(8); } + //! Return the size of the data area. + long sizeDataArea() const + { return value_.get() == 0 ? 0 : value_->sizeDataArea(); } + /*! + @brief Return a copy of the data area of the value. The caller owns + this copy and %DataBuf ensures that it will be deleted. + + Values may have a data area, which can contain additional + information besides the actual value. This method is used to access + such a data area. + + @return A %DataBuf containing a copy of the data area or an empty + %DataBuf if the value does not have a data area assigned or the + value is not set. + */ + DataBuf dataArea() const + { return value_.get() == 0 ? DataBuf(0, 0) : value_->dataArea(); } + + //@} + + private: + // DATA + ExifKey::AutoPtr key_; //!< Key + Value::AutoPtr value_; //!< Value + + }; // class Exifdatum + + /*! + @brief Output operator for Exifdatum types, prints the interpreted + tag value. + */ + std::ostream& operator<<(std::ostream& os, const Exifdatum& md); + + /*! + @brief Set the value of \em exifDatum to \em value. If the object already + has a value, it is replaced. Otherwise a new ValueType\<T\> value + is created and set to \em value. + + This is a helper function, called from Exifdatum members. It is meant to + be used with T = (u)int16_t, (u)int32_t or (U)Rational. Do not use directly. + */ + template<typename T> + Exifdatum& setValue(Exifdatum& exifDatum, const T& value); + + /*! + @brief Exif %Thumbnail image. This abstract base class provides the + interface for the thumbnail image that is optionally embedded in + the Exif data. This class is used internally by ExifData, it is + probably not useful for a client as a standalone class. Instead, + use an instance of ExifData to access the Exif thumbnail image. + */ + class Thumbnail { + public: + //! Shortcut for a %Thumbnail auto pointer. + typedef std::auto_ptr<Thumbnail> AutoPtr; + + //! @name Creators + //@{ + //! Virtual destructor + virtual ~Thumbnail() {} + //@} + + //! @name Accessors + //@{ + /*! + @brief Set the image data as data area of the appropriate Exif + metadatum. Read the thumbnail image data from data buffer + \em buf. Return 0 if successful. + + @param exifData Exif data corresponding to the data buffer. + @param pIfd1 Corresponding raw IFD1. + @param buf Data buffer containing the thumbnail data. The buffer must + start with the TIFF header. + @param len Number of bytes in the data buffer. + @return 0 if successful;<BR> + 1 in case of inconsistent thumbnail Exif data; or<BR> + 2 if the data area is outside of the data buffer + */ + virtual int setDataArea(ExifData& exifData, + Ifd* pIfd1, + const byte* buf, + long len) const =0; + /*! + @brief Return the thumbnail image in a %DataBuf. The caller owns the + data buffer and %DataBuf ensures that it will be deleted. + */ + virtual DataBuf copy(const ExifData& exifData) const =0; + /*! + @brief Return a short string for the format of the thumbnail + ("TIFF", "JPEG"). + */ + virtual const char* format() const =0; + /*! + @brief Return the file extension for the format of the thumbnail + (".tif", ".jpg"). + */ + virtual const char* extension() const =0; + //@} + + protected: + //! @name Manipulators + //@{ + /*! + @brief Assignment operator. Protected so that it can only be used + by subclasses but not directly. + */ + Thumbnail& operator=(const Thumbnail& rhs); + //@} + + }; // class Thumbnail + + //! Exif thumbnail image in TIFF format + class TiffThumbnail : public Thumbnail { + public: + //! Shortcut for a %TiffThumbnail auto pointer. + typedef std::auto_ptr<TiffThumbnail> AutoPtr; + + //! @name Manipulators + //@{ + //! Assignment operator. + TiffThumbnail& operator=(const TiffThumbnail& rhs) { return *this; } + //@} + + //! @name Accessors + //@{ + int setDataArea(ExifData& exifData, + Ifd* pIfd1, + const byte* buf, + long len) const; + DataBuf copy(const ExifData& exifData) const; + const char* format() const; + const char* extension() const; + //@} + + }; // class TiffThumbnail + + //! Exif thumbnail image in JPEG format + class JpegThumbnail : public Thumbnail { + public: + //! Shortcut for a %JpegThumbnail auto pointer. + typedef std::auto_ptr<JpegThumbnail> AutoPtr; + + //! @name Manipulators + //@{ + //! Assignment operator. + JpegThumbnail& operator=(const JpegThumbnail& rhs) { return *this; } + //@} + + //! @name Accessors + //@{ + int setDataArea(ExifData& exifData, + Ifd* pIfd1, + const byte* buf, + long len) const; + DataBuf copy(const ExifData& exifData) const; + const char* format() const; + const char* extension() const; + //@} + + }; // class JpegThumbnail + + //! Container type to hold all metadata + typedef std::vector<Exifdatum> ExifMetadata; + + //! Unary predicate that matches a Exifdatum with a given ifd id and idx + class FindMetadatumByIfdIdIdx { + public: + //! Constructor, initializes the object with the ifd id and idx to look for + FindMetadatumByIfdIdIdx(IfdId ifdId, int idx) + : ifdId_(ifdId), idx_(idx) {} + /*! + @brief Returns true if the ifd id and idx of the argument + \em exifdatum is equal to that of the object. + */ + bool operator()(const Exifdatum& exifdatum) const + { return ifdId_ == exifdatum.ifdId() && idx_ == exifdatum.idx(); } + + private: + IfdId ifdId_; + int idx_; + + }; // class FindMetadatumByIfdIdIdx + + /*! + @brief A container for Exif data. This is a top-level class of the %Exiv2 + library. The container holds Exifdatum objects. + + Provide high-level access to the Exif data of an image: + - read Exif information from JPEG files + - access metadata through keys and standard C++ iterators + - add, modify and delete metadata + - write Exif data to JPEG files + - extract Exif metadata to files, insert from these files + - extract and delete Exif thumbnail (JPEG and TIFF thumbnails) + */ + class ExifData { + public: + //! ExifMetadata iterator type + typedef ExifMetadata::iterator iterator; + //! ExifMetadata const iterator type + typedef ExifMetadata::const_iterator const_iterator; + + //! @name Creators + //@{ + //! Default constructor + ExifData(); + //! Copy constructor (Todo: copy image data also) + ExifData(const ExifData& rhs); + //! Destructor + ~ExifData(); + //@} + + //! @name Manipulators + //@{ + //! Assignment operator (Todo: assign image data also) + ExifData& operator=(const ExifData& rhs); + /*! + @brief Load the Exif data from a byte buffer. The data buffer + must start with the TIFF header. + @param buf Pointer to the data buffer to read from + @param len Number of bytes in the data buffer + @return 0 if successful. + */ + int load(const byte* buf, long len); + /*! + @brief Write the Exif data to a data buffer, which is returned. The + caller owns this copy and %DataBuf ensures that it will be + deleted. The copied data starts with the TIFF header. + + Tries to update the original data buffer and write it back with + minimal changes, in a 'non-intrusive' fashion, if possible. In this + case, tag data that ExifData does not understand stand a good chance + to remain valid. (In particular, if the Exif data contains a + Makernote in IFD format, the offsets in its IFD will remain valid.) + <BR> + If 'non-intrusive' writing is not possible, the Exif data will be + re-built from scratch, in which case the absolute position of the + metadata entries within the data buffer may (and in most cases will) + be different from their original position. Furthermore, in this case, + the Exif data is updated with the metadata from the actual thumbnail + image (overriding existing metadata). + + @return A %DataBuf containing the Exif data. + */ + DataBuf copy(); + /*! + @brief Returns a reference to the %Exifdatum that is associated with a + particular \em key. If %ExifData does not already contain such + an %Exifdatum, operator[] adds object \em Exifdatum(key). + + @note Since operator[] might insert a new element, it can't be a const + member function. + */ + Exifdatum& operator[](const std::string& key); + /*! + @brief Add all (IFD) entries in the range from iterator position begin + to iterator position end to the Exif metadata. No duplicate + checks are performed, i.e., it is possible to add multiple + metadata with the same key. + */ + void add(Entries::const_iterator begin, + Entries::const_iterator end, + ByteOrder byteOrder); + /*! + @brief Add an Exifdatum from the supplied key and value pair. This + method copies (clones) key and value. No duplicate checks are + performed, i.e., it is possible to add multiple metadata with + the same key. + */ + void add(const ExifKey& key, const Value* pValue); + /*! + @brief Add a copy of the \em exifdatum to the Exif metadata. No + duplicate checks are performed, i.e., it is possible to add + multiple metadata with the same key. + + @throw Error if the makernote cannot be created + */ + void add(const Exifdatum& exifdatum); + /*! + @brief Delete the Exifdatum at iterator position \em pos, return the + position of the next exifdatum. Note that iterators into + the metadata, including \em pos, are potentially invalidated + by this call. + */ + iterator erase(iterator pos); + /*! + @brief Delete all Exifdatum instances resulting in an empty container. + Note that this also removes thumbnails. + */ + void clear() { eraseThumbnail(); exifMetadata_.clear(); } + //! Sort metadata by key + void sortByKey(); + //! Sort metadata by tag + void sortByTag(); + //! Begin of the metadata + iterator begin() { return exifMetadata_.begin(); } + //! End of the metadata + iterator end() { return exifMetadata_.end(); } + /*! + @brief Find a Exifdatum with the given \em key, return an iterator to + it. If multiple metadata with the same key exist, it is + undefined which of the matching metadata is found. + */ + iterator findKey(const ExifKey& key); + /*! + @brief Find the Exifdatum with the given \em ifdId and \em idx, + return an iterator to it. + + This method can be used to uniquely identify an exifdatum that was + created from an IFD or from the makernote (with idx greater than + 0). Metadata created by an application (not read from an IFD or a + makernote) all have their idx field set to 0, i.e., they cannot be + uniquely identified with this method. If multiple metadata with the + same key exist, it is undefined which of the matching metadata is + found. + */ + iterator findIfdIdIdx(IfdId ifdId, int idx); + /*! + @brief Set the Exif thumbnail to the Jpeg image \em path. Set + XResolution, YResolution and ResolutionUnit to \em xres, + \em yres and \em unit, respectively. + + This results in the minimal thumbnail tags being set for a Jpeg + thumbnail, as mandated by the Exif standard. + + @throw Error if reading the file fails. + + @note No checks on the file format or size are performed. + @note Additional existing Exif thumbnail tags are not modified. + @note The Jpeg image inserted as thumbnail image should not + itself contain Exif data (or other metadata), as existing + applications may have problems with that. (The preview + application that comes with OS X for one.) - David Harvey. + */ + void setJpegThumbnail(const std::string& path, + URational xres, URational yres, uint16_t unit); + /*! + @brief Set the Exif thumbnail to the Jpeg image pointed to by \em buf, + and size \em size. Set XResolution, YResolution and + ResolutionUnit to \em xres, \em yres and \em unit, respectively. + + This results in the minimal thumbnail tags being set for a Jpeg + thumbnail, as mandated by the Exif standard. + + @throw Error if reading the file fails. + + @note No checks on the image format or size are performed. + @note Additional existing Exif thumbnail tags are not modified. + @note The Jpeg image inserted as thumbnail image should not + itself contain Exif data (or other metadata), as existing + applications may have problems with that. (The preview + application that comes with OS X for one.) - David Harvey. + */ + void setJpegThumbnail(const byte* buf, long size, + URational xres, URational yres, uint16_t unit); + /*! + @brief Set the Exif thumbnail to the Jpeg image \em path. + + This sets only the Compression, JPEGInterchangeFormat and + JPEGInterchangeFormatLength tags, which is not all the thumbnail + Exif information mandatory according to the Exif standard. (But it's + enough to work with the thumbnail.) + + @throw Error if reading the file fails. + + @note No checks on the file format or size are performed. + @note Additional existing Exif thumbnail tags are not modified. + */ + void setJpegThumbnail(const std::string& path); + /*! + @brief Set the Exif thumbnail to the Jpeg image pointed to by \em buf, + and size \em size. + + This sets only the Compression, JPEGInterchangeFormat and + JPEGInterchangeFormatLength tags, which is not all the thumbnail + Exif information mandatory according to the Exif standard. (But it's + enough to work with the thumbnail.) + + @note No checks on the image format or size are performed. + @note Additional existing Exif thumbnail tags are not modified. + */ + void setJpegThumbnail(const byte* buf, long size); + /*! + @brief Delete the thumbnail from the Exif data. Removes all + Exif.%Thumbnail.*, i.e., IFD1 metadata. + + @return The number of bytes of thumbnail data erased from the original + Exif data. Note that the original image size may differ from + the size of the image after deleting the thumbnail by more + than this number. This is the case if the Exif data contains + extra bytes (often at the end of the Exif block) or gaps and + the thumbnail is not located at the end of the Exif block so + that non-intrusive writing of a truncated Exif block is not + possible. Instead it is in this case necessary to write the + Exif data, without the thumbnail, from the metadata and all + extra bytes and gaps are lost, resulting in a smaller image. + */ + long eraseThumbnail(); + //@} + + //! @name Accessors + //@{ + //! Begin of the metadata + const_iterator begin() const { return exifMetadata_.begin(); } + //! End of the metadata + const_iterator end() const { return exifMetadata_.end(); } + /*! + @brief Find an exifdatum with the given \em key, return a const + iterator to it. If multiple metadata with the same key exist, + it is undefined which of the matching metadata is found. + */ + const_iterator findKey(const ExifKey& key) const; + /*! + @brief Find the exifdatum with the given \em ifdId and \em idx, + return an iterator to it. + + This method can be used to uniquely identify a Exifdatum that was + created from an IFD or from the makernote (with idx greater than + 0). Metadata created by an application (not read from an IFD or a + makernote) all have their idx field set to 0, i.e., they cannot be + uniquely identified with this method. If multiple metadata with the + same key exist, it is undefined which of the matching metadata is + found. + */ + const_iterator findIfdIdIdx(IfdId ifdId, int idx) const; + //! Return true if there is no Exif metadata + bool empty() const { return count() == 0; } + //! Get the number of metadata entries + long count() const { return static_cast<long>(exifMetadata_.size()); } + /*! + @brief Returns the byte order. Default is little endian. + */ + ByteOrder byteOrder() const; + /*! + @brief Write the thumbnail image to a file. A filename extension + is appended to \em path according to the image type of the + thumbnail, so \em path should not include an extension. + This will overwrite an existing file of the same name. + + @param path Path of the filename without image type extension + + @throw Error if writing to the file fails. + + @return 0 if successful;<BR> + 8 if the Exif data does not contain a thumbnail. + */ + int writeThumbnail(const std::string& path) const; + /*! + @brief Return the thumbnail image in a %DataBuf. The caller owns the + data buffer and %DataBuf ensures that it will be deleted. + */ + DataBuf copyThumbnail() const; + /*! + @brief Return a short string describing the format of the Exif + thumbnail ("TIFF", "JPEG"). + */ + const char* thumbnailFormat() const; + /*! + @brief Return the file extension for the Exif thumbnail depending + on the format (".tif", ".jpg"). + */ + const char* thumbnailExtension() const; + /*! + @brief Return a thumbnail object of the correct type, corresponding to + the current Exif data. Caller owns this object and the auto + pointer ensures that it will be deleted. + */ + Thumbnail::AutoPtr getThumbnail() const; + //@} + + private: + //! @name Manipulators + //@{ + /*! + @brief Read the thumbnail from the data buffer. Assigns the thumbnail + data area with the appropriate Exif tags. Return 0 if successful, + i.e., if there is a thumbnail. + */ + int readThumbnail(); + /*! + @brief Check if the metadata changed and update the internal IFDs and + the MakerNote if the changes are compatible with the existing + data (non-intrusive write support). + + @return True if only compatible changes were detected in the metadata + and the internal IFDs and MakerNote (and thus the data buffer) + were updated successfully. Return false, if non-intrusive + writing is not possible. The internal IFDs and the MakerNote + (and thus the data buffer) may or may not be modified in this + case. + */ + bool updateEntries(); + /*! + @brief Update the metadata for a range of entries. Called by + updateEntries() for each of the internal IFDs and the MakerNote + (if any). + */ + bool updateRange(const Entries::iterator& begin, + const Entries::iterator& end, + ByteOrder byteOrder); + /*! + @brief Write the Exif data to a data buffer the hard way, return the + data buffer. The caller owns this data buffer and %DataBuf + ensures that it will be deleted. + + Rebuilds the Exif data from scratch, using the TIFF header, metadata + container and thumbnail. In particular, the internal IFDs and the + original data buffer are not used. Furthermore, this method updates + the Exif data with the metadata from the actual thumbnail image + (overriding existing metadata). + + @return A %DataBuf containing the Exif data. + */ + DataBuf copyFromMetadata(); + //@} + + //! @name Accessors + //@{ + /*! + @brief Check if the metadata is compatible with the internal IFDs for + non-intrusive writing. Return true if compatible, false if not. + + @note This function does not detect deleted metadata as incompatible, + although the deletion of metadata is not (yet) a supported + non-intrusive write operation. + */ + bool compatible() const; + /*! + @brief Find the IFD or makernote entry corresponding to ifd id and idx. + + @return A pair of which the first part determines if a match was found + and, if true, the second contains an iterator to the entry. + */ + std::pair<bool, Entries::const_iterator> + findEntry(IfdId ifdId, int idx) const; + //! Return a pointer to the internal IFD identified by its IFD id + const Ifd* getIfd(IfdId ifdId) const; + /*! + @brief Check if IFD1, the IFD1 data and thumbnail data are located at + the end of the Exif data. Return true, if they are or if there + is no thumbnail at all, else return false. + */ + bool stdThumbPosition() const; + //@} + + // DATA + ExifMetadata exifMetadata_; + + // The pointers below are used only if Exif data is read from a + // raw data buffer + TiffHeader* pTiffHeader_; //! Pointer to the TIFF header + Ifd* pIfd0_; //! Pointer to Ifd0 + Ifd* pExifIfd_; //! Pointer to ExifIfd + Ifd* pIopIfd_; //! Pointer to IopIfd + Ifd* pGpsIfd_; //! Pointer to GpsIfd + Ifd* pIfd1_; //! Pointer to Ifd1 + MakerNote* pMakerNote_; //! Pointer to the MakerNote, if any + + long size_; //!< Size of the Exif raw data in bytes + byte* pData_; //!< Exif raw data buffer + + /*! + Can be set to false to indicate that non-intrusive writing is not + possible. If it is true (the default), then the compatibility checks + will be performed to determine which writing method to use. + */ + bool compatible_; + + }; // class ExifData + +// ***************************************************************************** +// template, inline and free functions + + template<typename T> + Exifdatum& setValue(Exifdatum& exifDatum, const T& value) + { + std::auto_ptr<ValueType<T> > v + = std::auto_ptr<ValueType<T> >(new ValueType<T>); + v->value_.push_back(value); + exifDatum.value_ = v; + return exifDatum; + } + /*! + @brief Add all metadata in the range from iterator position begin to + iterator position end, which have an IFD id matching that of the + IFD to the list of directory entries of ifd. No duplicate checks + are performed, i.e., it is possible to add multiple metadata with + the same key to an IFD. + */ + void addToIfd(Ifd& ifd, + ExifMetadata::const_iterator begin, + ExifMetadata::const_iterator end, + ByteOrder byteOrder); + /*! + @brief Add the Exifdatum to the IFD. No duplicate checks are performed, + i.e., it is possible to add multiple metadata with the same key to + an IFD. + */ + void addToIfd(Ifd& ifd, const Exifdatum& exifdatum, ByteOrder byteOrder); + /*! + @brief Add all metadata in the range from iterator position begin to + iterator position end with IFD id 'makerIfd' to the list of + makernote entries of the object pointed to be makerNote. No + duplicate checks are performed, i.e., it is possible to add + multiple metadata with the same key to a makernote. + */ + void addToMakerNote(MakerNote* makerNote, + ExifMetadata::const_iterator begin, + ExifMetadata::const_iterator end, + ByteOrder byteOrder); + /*! + @brief Add the Exifdatum to makerNote, encoded in byte order byteOrder. + No duplicate checks are performed, i.e., it is possible to add + multiple metadata with the same key to a makernote. + */ + void addToMakerNote(MakerNote* makerNote, + const Exifdatum& exifdatum, + ByteOrder byteOrder); + +} // namespace Exiv2 + +#endif // #ifndef EXIF_HPP_ diff --git a/src/plugins/exiv2/exifcomment.cpp b/src/plugins/exiv2/exifcomment.cpp @@ -0,0 +1,68 @@ +// ***************************************************************** -*- C++ -*- +/* + Abstract : Sample program showing how to set the Exif comment of an image, + Exif.Photo.UserComment + + File: exifcomment.cpp + Version : $Rev: 560 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History : 10-May-04, ahu: created + 16-Jan-05, ahu: updated using CommentValue and operator trickery + */ +// ***************************************************************************** +// included header files +#include "image.hpp" +#include "exif.hpp" +#include <iostream> +#include <iomanip> +#include <cstring> +#include <cassert> + +// ***************************************************************************** +// Main +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(); + Exiv2::ExifData &exifData = image->exifData(); + + /* + Exiv2 uses a CommentValue for Exif user comments. The format of the + comment string includes an optional charset specification at the beginning: + + [charset=["]Ascii|Jis|Unicode|Undefined["] ]comment + + Undefined is used as a default if the comment doesn't start with a charset + definition. + + Following are a few examples of valid comments. The last one is written to + the file. + */ + exifData["Exif.Photo.UserComment"] + = "charset=\"Unicode\" An Unicode Exif comment added with Exiv2"; + exifData["Exif.Photo.UserComment"] + = "charset=\"Undefined\" An undefined Exif comment added with Exiv2"; + exifData["Exif.Photo.UserComment"] + = "Another undefined Exif comment added with Exiv2"; + exifData["Exif.Photo.UserComment"] + = "charset=Ascii An ASCII Exif comment added with Exiv2"; + + std::cout << "Writing user comment '" + << exifData["Exif.Photo.UserComment"] + << "' back to the image\n"; + + image->writeMetadata(); + + return 0; +} +catch (Exiv2::AnyError& e) { + std::cout << "Caught Exiv2 exception '" << e << "'\n"; + return -1; +} diff --git a/src/plugins/exiv2/exifprint.cpp b/src/plugins/exiv2/exifprint.cpp @@ -0,0 +1,49 @@ +// ***************************************************************** -*- C++ -*- +// exifprint.cpp, $Rev: 578 $ +// Sample program to print the Exif metadata of an image + +#include "image.hpp" +#include "exif.hpp" +#include <iostream> +#include <iomanip> +#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(); + + Exiv2::ExifData &exifData = image->exifData(); + if (exifData.empty()) { + std::string error(argv[1]); + error += ": No Exif data found in the file"; + throw Exiv2::Error(1, error); + } + Exiv2::ExifData::const_iterator end = exifData.end(); + for (Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) { + std::cout << std::setw(44) << std::setfill(' ') << std::left + << i->key() << " " + << "0x" << std::setw(4) << std::setfill('0') << std::right + << std::hex << i->tag() << " " + << std::setw(9) << std::setfill(' ') << std::left + << i->typeName() << " " + << std::dec << std::setw(3) + << std::setfill(' ') << std::right + << i->count() << " " + << std::dec << i->value() + << "\n"; + } + + return 0; +} +catch (Exiv2::AnyError& e) { + std::cout << "Caught Exiv2 exception '" << e << "'\n"; + return -1; +} diff --git a/src/plugins/exiv2/exiv2.cpp b/src/plugins/exiv2/exiv2.cpp @@ -0,0 +1,804 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + Abstract: Command line program to display and manipulate image %Exif data + + File: exiv2.cpp + Version: $Rev: 575 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 10-Dec-03, ahu: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: exiv2.cpp 575 2005-06-04 07:32:20Z ahuggel $"); + +// ***************************************************************************** +// included header files +#ifdef _MSC_VER +# include "exv_msvc.h" +#else +# include "exv_conf.h" +#endif + +#include "exiv2.hpp" +#include "actions.hpp" +#include "utils.hpp" + +#include <string> +#include <iostream> +#include <fstream> +#include <iomanip> +#include <cstring> +#include <cassert> + +// ***************************************************************************** +// local declarations +namespace { + + //! List of all command identifiers and corresponding strings + static const CmdIdAndString cmdIdAndString[] = { + { add, "add" }, + { set, "set" }, + { del, "del" }, + { invalidCmdId, "invalidCmd" } // End of list marker + }; + + // Return a command Id for a command string + CmdId commandId(const std::string& cmdString); + + // Evaluate [-]HH[:MM[:SS]], returns true and sets time to the value + // in seconds if successful, else returns false. + bool parseTime(const std::string& ts, long& time); + + /*! + @brief Parse the oparg string into a bitmap of common targets. + @param optarg Option arguments + @param action Action being processed + @return A bitmap of common targets or -1 in case of a parse error + */ + int parseCommonTargets(const std::string& optarg, + const std::string& action); + + /*! + @brief Parse metadata modification commands from multiple files + @param modifyCmds Reference to a structure to store the parsed commands + @param cmdFiles Container with the file names + */ + bool parseCmdFiles(ModifyCmds& modifyCmds, + const Params::CmdFiles& cmdFiles); + + /*! + @brief Parse metadata modification commands from a container of commands + @param modifyCmds Reference to a structure to store the parsed commands + @param cmdLines Container with the commands + */ + bool parseCmdLines(ModifyCmds& modifyCmds, + const Params::CmdLines& cmdLines); + + /*! + @brief Parse one line of the command file + @param modifyCmd Reference to a command structure to store the parsed + command + @param line Input line + @param num Line number (used for error output) + */ + bool parseLine(ModifyCmd& modifyCmd, + const std::string& line, int num); + +} + +// ***************************************************************************** +// Main +int main(int argc, char* const argv[]) +{ + // Handle command line arguments + Params& params = Params::instance(); + if (params.getopt(argc, argv)) { + params.usage(); + return 1; + } + if (params.help_) { + params.help(); + return 0; + } + if (params.version_) { + params.version(); + return 0; + } + + // Create the required action class + Action::TaskFactory& taskFactory = Action::TaskFactory::instance(); + Action::Task::AutoPtr task + = taskFactory.create(Action::TaskType(params.action_)); + assert(task.get()); + + // Process all files + int n = 1; + int s = static_cast<int>(params.files_.size()); + int w = s > 9 ? s > 99 ? 3 : 2 : 1; + Params::Files::const_iterator e = params.files_.end(); + for (Params::Files::const_iterator i = params.files_.begin(); i != e; ++i) { + if (params.verbose_) { + std::cout << "File " << std::setw(w) << n++ << "/" << s << ": " + << *i << std::endl; + } + task->run(*i); + } + return 0; +} // main + +// ***************************************************************************** +// class Params +Params* Params::instance_ = 0; + +Params& Params::instance() +{ + if (0 == instance_) { + instance_ = new Params; + } + return *instance_; +} + +void Params::version(std::ostream& os) const +{ + os << EXV_PACKAGE_STRING << ", " + << "Copyright (C) 2004, 2005 Andreas Huggel.\n\n" + << "This is free software; see the source for copying conditions. " + << "There is NO \nwarranty; not even for MERCHANTABILITY or FITNESS FOR " + << "A PARTICULAR PURPOSE.\n"; +} + +void Params::usage(std::ostream& os) const +{ + os << "Usage: " << progname() + << " [ options ] [ action ] file ...\n\n" + << "Manipulate the Exif metadata of images.\n"; +} + +void Params::help(std::ostream& os) const +{ + usage(os); + os << "\nActions:\n" + << " ad | adjust Adjust Exif timestamps by the given time. This\n" + << " action requires the option -a time.\n" + << " pr | print Print image metadata.\n" + << " rm | delete Delete image metadata from the files.\n" + << " in | insert Insert metadata from corresponding *.exv files.\n" + << " ex | extract Extract metadata to *.exv and thumbnail image files.\n" + << " mv | rename Rename files according to the Exif create timestamp.\n" + << " The filename format can be set with -r format.\n" + << " mo | modify Apply commands to modify (add, set, delete) the Exif\n" + << " and Iptc metadata of image files. Requires option -m or -M\n" + << "\nOptions:\n" + << " -h Display this help and exit.\n" + << " -V Show the program version and exit.\n" + << " -v Be verbose during the program run.\n" + << " -f Do not prompt before overwriting existing files (force).\n" + << " -F Do not prompt before renaming existing files (Force).\n" + << " -a time Time adjustment in the format [-]HH[:MM[:SS]]. This option\n" + << " is only used with the `adjust' action.\n" + << " -p mode Print mode for the `print' action. Possible modes are:\n" + << " s : print a summary of the Exif metadata (the default)\n" + << " t : interpreted (translated) Exif data\n" + << " v : plain Exif data values\n" + << " h : hexdump of the Exif data\n" + << " i : Iptc data values\n" + << " c : Jpeg comment\n" + << " -d tgt Delete target(s) for the `delete' action. Possible targets are:\n" + << " 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 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" + << " -e tgt Extract target(s) for the `extract' action. Possible targets\n" + << " are the same as those for the -d option.\n" + << " -r fmt Filename format for the `rename' action. The format string\n" + << " follows strftime(3). Default filename format is " + << format_ << ".\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" + << " commands is the same as that of the lines of a command file.\n" + << " -l dir Location (directory) for files to be inserted or extracted.\n\n"; +} // Params::help + +int Params::option(int opt, const std::string& optarg, int optopt) +{ + int rc = 0; + switch (opt) { + case 'h': help_ = true; break; + case 'V': version_ = true; break; + case 'v': verbose_ = true; break; + case 'f': force_ = true; fileExistsPolicy_ = overwritePolicy; break; + case 'F': force_ = true; fileExistsPolicy_ = renamePolicy; break; + case 'r': rc = evalRename(optarg); break; + case 'a': rc = evalAdjust(optarg); break; + case 'p': rc = evalPrint(optarg); break; + case 'd': rc = evalDelete(optarg); break; + case 'e': rc = evalExtract(optarg); break; + case 'i': rc = evalInsert(optarg); break; + case 'm': rc = evalModify(opt, optarg); break; + case 'M': rc = evalModify(opt, optarg); break; + case 'l': directory_ = optarg; break; + case ':': + std::cerr << progname() << ": Option -" << static_cast<char>(optopt) + << " requires an argument\n"; + rc = 1; + break; + case '?': + std::cerr << progname() << ": Unrecognized option -" + << static_cast<char>(optopt) << "\n"; + rc = 1; + break; + default: + std::cerr << progname() + << ": getopt returned unexpected character code " + << std::hex << opt << "\n"; + rc = 1; + break; + } + return rc; +} // Params::option + +int Params::evalRename(const std::string& optarg) +{ + int rc = 0; + switch (action_) { + case Action::none: + action_ = Action::rename; + format_ = optarg; + break; + case Action::rename: + std::cerr << progname() + << ": Ignoring surplus option -r \"" << optarg << "\"\n"; + break; + default: + std::cerr << progname() + << ": Option -r is not compatible with a previous option\n"; + rc = 1; + break; + } + return rc; +} // Params::evalRename + +int Params::evalAdjust(const std::string& optarg) +{ + int rc = 0; + switch (action_) { + case Action::none: + action_ = Action::adjust; + adjust_ = parseTime(optarg, adjustment_); + if (!adjust_) { + std::cerr << progname() << ": Error parsing -a option argument `" + << optarg << "'\n"; + rc = 1; + } + break; + case Action::adjust: + std::cerr << progname() + << ": Ignoring surplus option -a " << optarg << "\n"; + break; + default: + std::cerr << progname() + << ": Option -a is not compatible with a previous option\n"; + rc = 1; + break; + } + return rc; +} // Params::evalAdjust + +int Params::evalPrint(const std::string& optarg) +{ + int rc = 0; + switch (action_) { + case Action::none: + action_ = Action::print; + switch (optarg[0]) { + case 's': printMode_ = pmSummary; break; + case 't': printMode_ = pmInterpreted; break; + case 'v': printMode_ = pmValues; break; + case 'h': printMode_ = pmHexdump; break; + case 'i': printMode_ = pmIptc; break; + case 'c': printMode_ = pmComment; break; + default: + std::cerr << progname() << ": Unrecognized print mode `" + << optarg << "'\n"; + rc = 1; + break; + } + break; + case Action::print: + std::cerr << progname() + << ": Ignoring surplus option -p" << optarg << "\n"; + break; + default: + std::cerr << progname() + << ": Option -p is not compatible with a previous option\n"; + rc = 1; + break; + } + return rc; +} // Params::evalPrint + +int Params::evalDelete(const std::string& optarg) +{ + int rc = 0; + switch (action_) { + case Action::none: + action_ = Action::erase; + target_ = 0; + // fallthrough + case Action::erase: + rc = parseCommonTargets(optarg, "erase"); + if (rc > 0) { + target_ |= rc; + rc = 0; + } + else { + rc = 1; + } + break; + default: + std::cerr << progname() + << ": Option -d is not compatible with a previous option\n"; + rc = 1; + break; + } + return rc; +} // Params::evalDelete + +int Params::evalExtract(const std::string& optarg) +{ + int rc = 0; + switch (action_) { + case Action::none: + action_ = Action::extract; + target_ = 0; + // fallthrough + case Action::extract: + rc = parseCommonTargets(optarg, "extract"); + if (rc > 0) { + target_ |= rc; + rc = 0; + } + else { + rc = 1; + } + break; + default: + std::cerr << progname() + << ": Option -e is not compatible with a previous option\n"; + rc = 1; + break; + } + return rc; +} // Params::evalExtract + +int Params::evalInsert(const std::string& optarg) +{ + int rc = 0; + switch (action_) { + case Action::none: + action_ = Action::insert; + target_ = 0; + // fallthrough + case Action::insert: + rc = parseCommonTargets(optarg, "insert"); + if (rc > 0) { + target_ |= rc; + rc = 0; + } + else { + rc = 1; + } + break; + default: + std::cerr << progname() + << ": Option -i is not compatible with a previous option\n"; + rc = 1; + break; + } + return rc; +} // Params::evalInsert + +int Params::evalModify(int opt, const std::string& optarg) +{ + int rc = 0; + switch (action_) { + case Action::none: + action_ = Action::modify; + // fallthrough + case Action::modify: + if (opt == 'm') cmdFiles_.push_back(optarg); // parse the files later + if (opt == 'M') cmdLines_.push_back(optarg); // parse the commands later + break; + default: + std::cerr << progname() + << ": Option -" << (char)opt + << " is not compatible with a previous option\n"; + rc = 1; + break; + } + return rc; +} // Params::evalModify + +int Params::nonoption(const std::string& argv) +{ + int rc = 0; + bool action = false; + if (first_) { + // The first non-option argument must be the action + first_ = false; + if (argv == "ad" || argv == "adjust") { + if (action_ != Action::none && action_ != Action::adjust) { + std::cerr << progname() << ": Action adjust is not " + << "compatible with the given options\n"; + rc = 1; + } + action = true; + action_ = Action::adjust; + } + if (argv == "pr" || argv == "print") { + if (action_ != Action::none && action_ != Action::print) { + std::cerr << progname() << ": Action print is not " + << "compatible with the given options\n"; + rc = 1; + } + action = true; + action_ = Action::print; + } + if (argv == "rm" || argv == "delete") { + if (action_ != Action::none && action_ != Action::erase) { + std::cerr << progname() << ": Action delete is not " + << "compatible with the given options\n"; + rc = 1; + } + action = true; + action_ = Action::erase; + } + if (argv == "ex" || argv == "extract") { + if (action_ != Action::none && action_ != Action::extract) { + std::cerr << progname() << ": Action extract is not " + << "compatible with the given options\n"; + rc = 1; + } + action = true; + action_ = Action::extract; + } + if (argv == "in" || argv == "insert") { + if (action_ != Action::none && action_ != Action::insert) { + std::cerr << progname() << ": Action insert is not " + << "compatible with the given options\n"; + rc = 1; + } + action = true; + action_ = Action::insert; + } + if (argv == "mv" || argv == "rename") { + if (action_ != Action::none && action_ != Action::rename) { + std::cerr << progname() << ": Action rename is not " + << "compatible with the given options\n"; + rc = 1; + } + action = true; + action_ = Action::rename; + } + if (argv == "mo" || argv == "modify") { + if (action_ != Action::none && action_ != Action::modify) { + std::cerr << progname() << ": Action modify is not " + << "compatible with the given options\n"; + rc = 1; + } + action = true; + action_ = Action::modify; + } + if (action_ == Action::none) { + // if everything else fails, assume print as the default action + action_ = Action::print; + } + } + if (!action) { + files_.push_back(argv); + } + return rc; +} // Params::nonoption + +int Params::getopt(int argc, char* const argv[]) +{ + int rc = Util::Getopt::getopt(argc, argv, optstring_); + // Further consistency checks + if (help_ || version_) return 0; + if (action_ == Action::none) { + // This shouldn't happen since print is taken as default action + std::cerr << progname() << ": An action must be specified\n"; + rc = 1; + } + if (action_ == Action::adjust && !adjust_) { + std::cerr << progname() + << ": Adjust action requires option -a time\n"; + rc = 1; + } + if (action_ == Action::modify && cmdFiles_.empty() && cmdLines_.empty()) { + std::cerr << progname() + << ": Modify action requires at least one -m or -M option\n"; + rc = 1; + } + if (0 == files_.size()) { + std::cerr << progname() << ": At least one file is required\n"; + rc = 1; + } + if (rc == 0 && action_ == Action::modify) { + // Parse command files + if (!parseCmdFiles(modifyCmds_, cmdFiles_)) { + std::cerr << progname() << ": Error parsing -m option arguments\n"; + rc = 1; + } + } + if (rc ==0 && action_ == Action::modify) { + // Parse command lines + if (!parseCmdLines(modifyCmds_, cmdLines_)) { + std::cerr << progname() << ": Error parsing -M option arguments\n"; + rc = 1; + } + } + if (!directory_.empty() && !(action_ == Action::insert || action_ == Action::extract)) { + std::cerr << progname() << ": -l option can only be used with extract or insert actions\n"; + rc = 1; + } + return rc; +} // Params::getopt + +// ***************************************************************************** +// local implementations +namespace { + + bool parseTime(const std::string& ts, long& time) + { + std::string hstr, mstr, sstr; + char *cts = new char[ts.length() + 1]; + strcpy(cts, ts.c_str()); + char *tmp = ::strtok(cts, ":"); + if (tmp) hstr = tmp; + tmp = ::strtok(0, ":"); + if (tmp) mstr = tmp; + tmp = ::strtok(0, ":"); + if (tmp) sstr = tmp; + delete[] cts; + + int sign = 1; + long hh(0), mm(0), ss(0); + // [-]HH part + if (!Util::strtol(hstr.c_str(), hh)) return false; + if (hh < 0) { + sign = -1; + hh *= -1; + } + // check for the -0 special case + if (hh == 0 && hstr.find('-') != std::string::npos) sign = -1; + // MM part, if there is one + if (mstr != "") { + if (!Util::strtol(mstr.c_str(), mm)) return false; + if (mm > 59) return false; + if (mm < 0) return false; + } + // SS part, if there is one + if (sstr != "") { + if (!Util::strtol(sstr.c_str(), ss)) return false; + if (ss > 59) return false; + if (ss < 0) return false; + } + + time = sign * (hh * 3600 + mm * 60 + ss); + return true; + } // parseTime + + int parseCommonTargets(const std::string& optarg, + const std::string& action) + { + int rc = 0; + int target = 0; + for (size_t i = 0; rc == 0 && i < optarg.size(); ++i) { + switch (optarg[i]) { + case 'e': target |= Params::ctExif; break; + case 'i': target |= Params::ctIptc; break; + case 'c': target |= Params::ctComment; break; + case 't': target |= Params::ctThumb; break; + case 'a': target |= Params::ctExif + | Params::ctIptc + | Params::ctComment; break; + default: + std::cerr << Params::instance().progname() << ": Unrecognized " + << action << " target `" << optarg[i] << "'\n"; + rc = -1; + break; + } + } + return rc ? rc : target; + } // parseCommonTargets + + bool parseCmdFiles(ModifyCmds& modifyCmds, + const Params::CmdFiles& cmdFiles) + { + Params::CmdFiles::const_iterator end = cmdFiles.end(); + Params::CmdFiles::const_iterator filename = cmdFiles.begin(); + for ( ; filename != end; ++filename) { + try { + std::ifstream file(filename->c_str()); + if (!file) { + std::cerr << *filename + << ": Failed to open command file for reading\n"; + return false; + } + int num = 0; + std::string line; + while (std::getline(file, line)) { + ModifyCmd modifyCmd; + if (parseLine(modifyCmd, line, ++num)) { + modifyCmds.push_back(modifyCmd); + } + } + } + catch (const Exiv2::AnyError& error) { + std::cerr << *filename << ", line " << error << "\n"; + return false; + } + } + return true; + } // parseCmdFile + + bool parseCmdLines(ModifyCmds& modifyCmds, + const Params::CmdLines& cmdLines) + { + try { + int num = 0; + Params::CmdLines::const_iterator end = cmdLines.end(); + Params::CmdLines::const_iterator line = cmdLines.begin(); + for ( ; line != end; ++line) { + ModifyCmd modifyCmd; + if (parseLine(modifyCmd, *line, ++num)) { + modifyCmds.push_back(modifyCmd); + } + } + return true; + } + catch (const Exiv2::AnyError& error) { + std::cerr << "-M option " << error << "\n"; + return false; + } + } // parseCmdLines + + bool parseLine(ModifyCmd& modifyCmd, const std::string& line, int num) + { + const std::string delim = " \t"; + + // Skip empty lines and comments + std::string::size_type cmdStart = line.find_first_not_of(delim); + if (cmdStart == std::string::npos || line[cmdStart] == '#') return false; + + // Get command and key + std::string::size_type cmdEnd = line.find_first_of(delim, cmdStart+1); + std::string::size_type keyStart = line.find_first_not_of(delim, cmdEnd+1); + std::string::size_type keyEnd = line.find_first_of(delim, keyStart+1); + if ( cmdStart == std::string::npos + || cmdEnd == std::string::npos + || keyStart == std::string::npos) { + throw Exiv2::Error(1, Exiv2::toString(num) + + ": Invalid command line"); + } + + std::string cmd(line.substr(cmdStart, cmdEnd-cmdStart)); + CmdId cmdId = commandId(cmd); + if (cmdId == invalidCmdId) { + throw Exiv2::Error(1, Exiv2::toString(num) + + ": Invalid command `" + cmd + "'"); + } + + Exiv2::TypeId defaultType = Exiv2::invalidTypeId; + std::string key(line.substr(keyStart, keyEnd-keyStart)); + MetadataId metadataId = invalidMetadataId; + try { + Exiv2::IptcKey iptcKey(key); + metadataId = iptc; + defaultType = Exiv2::IptcDataSets::dataSetType(iptcKey.tag(), + iptcKey.record()); + } + catch (const Exiv2::AnyError&) {} + if (metadataId == invalidMetadataId) { + try { + Exiv2::ExifKey exifKey(key); + metadataId = exif; + defaultType = Exiv2::ExifTags::tagType(exifKey.tag(), + exifKey.ifdId()); + } + catch (const Exiv2::AnyError&) {} + } + if (metadataId == invalidMetadataId) { + throw Exiv2::Error(1, Exiv2::toString(num) + + ": Invalid key `" + key + "'"); + } + + std::string value; + Exiv2::TypeId type = defaultType; + bool explicitType = false; + if (cmdId != del) { + // Get type and value + std::string::size_type typeStart + = line.find_first_not_of(delim, keyEnd+1); + std::string::size_type typeEnd + = line.find_first_of(delim, typeStart+1); + std::string::size_type valStart = typeStart; + std::string::size_type valEnd = line.find_last_not_of(delim); + + if ( keyEnd == std::string::npos + || typeStart == std::string::npos + || valStart == std::string::npos) { + throw Exiv2::Error(1, Exiv2::toString(num) + + ": Invalid command line "); + } + + if (typeEnd != std::string::npos) { + std::string typeStr(line.substr(typeStart, typeEnd-typeStart)); + Exiv2::TypeId tmpType = Exiv2::TypeInfo::typeId(typeStr); + if (tmpType != Exiv2::invalidTypeId) { + valStart = line.find_first_not_of(delim, typeEnd+1); + if (valStart == std::string::npos) { + throw Exiv2::Error(1, Exiv2::toString(num) + + ": Invalid command line "); + } + type = tmpType; + explicitType = true; + } + } + + value = line.substr(valStart, valEnd+1-valStart); + std::string::size_type last = value.length()-1; + if ( (value[0] == '"' || value[last] == '"') + && value[0] != value[last]) { + throw Exiv2::Error(1, Exiv2::toString(num) + + ": Unbalanced quotes"); + } + if (value[0] == '"') { + value = value.substr(1, value.length()-2); + } + } + + modifyCmd.cmdId_ = cmdId; + modifyCmd.key_ = key; + modifyCmd.metadataId_ = metadataId; + modifyCmd.typeId_ = type; + modifyCmd.explicitType_ = explicitType; + modifyCmd.value_ = value; + + return true; + } // parseLine + + CmdId commandId(const std::string& cmdString) + { + int i = 0; + for (; cmdIdAndString[i].cmdId_ != invalidCmdId + && cmdIdAndString[i].cmdString_ != cmdString; ++i) {} + return cmdIdAndString[i].cmdId_; + } + +} diff --git a/src/plugins/exiv2/exiv2.hpp b/src/plugins/exiv2/exiv2.hpp @@ -0,0 +1,217 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file exiv2.hpp + @brief Defines class Params, used for the command line handling of exiv2 + @version $Rev: 567 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 08-Dec-03, ahu: created + */ +#ifndef EXIV2_HPP_ +#define EXIV2_HPP_ + +// ***************************************************************************** +// included header files +#include "utils.hpp" +#include "types.hpp" + +// + standard includes +#include <string> +#include <vector> +#include <iostream> + +// ***************************************************************************** +// class definitions + +//! Command identifiers +enum CmdId { invalidCmdId, add, set, del }; +//! Metadata identifiers +enum MetadataId { invalidMetadataId, iptc, exif }; +//! Structure for one parsed modification command +struct ModifyCmd { + //! C'tor + ModifyCmd() : + cmdId_(invalidCmdId), metadataId_(invalidMetadataId), + typeId_(Exiv2::invalidTypeId), explicitType_(false) {} + CmdId cmdId_; //!< Command identifier + std::string key_; //!< Exiv2 key string + MetadataId metadataId_; //!< Metadata identifier + Exiv2::TypeId typeId_; //!< Exiv2 type identifier + //! Flag to indicate if the type was explicitely specified (true) + bool explicitType_; + std::string value_; //!< Data +}; +//! Container for modification commands +typedef std::vector<ModifyCmd> ModifyCmds; +//! Structure to link command identifiers to strings +struct CmdIdAndString { + CmdId cmdId_; //!< Commands identifier + std::string cmdString_; //!< Command string +}; + +/*! + @brief Implements the command line handling for the program. + + Derives from Util::Getopt to use the command line argument parsing + functionalty provided there. This class is implemented as a Singleton, + i.e., there is only one global instance of it, which can be accessed + from everywhere. + + <b>Usage example:</b> <br> + @code + #include "params.h" + + int main(int argc, char* const argv[]) + { + Params& params = Params::instance(); + if (params.getopt(argc, argv)) { + params.usage(); + return 1; + } + if (params.help_) { + params.help(); + return 0; + } + if (params.version_) { + params.version(); + return 0; + } + + // do something useful here... + + return 0; + } + @endcode + */ +class Params : public Util::Getopt { +private: + std::string optstring_; + +public: + //! Container for command files + typedef std::vector<std::string> CmdFiles; + //! Container for commands from the command line + typedef std::vector<std::string> CmdLines; + //! Container to store filenames. + typedef std::vector<std::string> Files; + + /*! + @brief Controls all access to the global Params instance. + @return Reference to the global Params instance. + */ + static Params& instance(); + + //! Enumerates print modes + enum PrintMode { pmSummary, pmInterpreted, pmValues, pmHexdump, pmIptc, + pmComment }; + //! Enumerates common targets, bitmap + enum CommonTarget { ctExif = 1, ctIptc = 2, ctComment = 4, ctThumb = 8 }; + //! Enumerates the policies to handle existing files in rename action + enum FileExistsPolicy { overwritePolicy, renamePolicy, askPolicy }; + + bool help_; //!< Help option flag. + bool version_; //!< Version option flag. + bool verbose_; //!< Verbose (talkative) option flag. + bool force_; //!< Force overwrites flag. + FileExistsPolicy fileExistsPolicy_; //!< What to do if file to rename exists. + bool adjust_; //!< Adjustment flag. + PrintMode printMode_; //!< Print mode. + //! %Action (integer rather than TaskType to avoid dependency). + int action_; + int target_; //!< What common target to process. + + long adjustment_; //!< Adjustment in seconds. + std::string format_; //!< Filename format (-r option arg). + CmdFiles cmdFiles_; //!< Names of the modification command files + CmdLines cmdLines_; //!< Commands from the command line + ModifyCmds modifyCmds_; //!< Parsed modification commands + std::string directory_; //!< Location for files to extract/insert + Files files_; //!< List of non-option arguments. + +private: + /*! + @brief Default constructor. Note that optstring_ is initialized here. + The c'tor is private to force instantiation through instance(). + */ + Params() : optstring_(":hVvfFa:r:p:d:e:i:m:M:l:"), + help_(false), + version_(false), + verbose_(false), + force_(false), + fileExistsPolicy_(askPolicy), + adjust_(false), + printMode_(pmSummary), + action_(0), + target_(ctExif|ctIptc|ctComment), + adjustment_(0), + format_("%Y%m%d_%H%M%S"), + first_(true) {} + + //! Prevent copy-construction: not implemented. + Params(const Params& rhs); + + //! @name Helpers + //@{ + int evalRename(const std::string& optarg); + int evalAdjust(const std::string& optarg); + int evalPrint(const std::string& optarg); + int evalDelete(const std::string& optarg); + int evalExtract(const std::string& optarg); + int evalInsert(const std::string& optarg); + int evalModify(int opt, const std::string& optarg); + //@} + + //! Pointer to the global Params object. + static Params* instance_; + + bool first_; + +public: + /*! + @brief Call Getopt::getopt() with optstring, to inititate command line + argument parsing, perform consistency checks after all command line + arguments are parsed. + + @param argc Argument count as passed to main() on program invocation. + @param argv Argument array as passed to main() on program invocation. + + @return 0 if successful, >0 in case of errors. + */ + int getopt(int argc, char* const argv[]); + + //! Handle options and their arguments. + virtual int option(int opt, const std::string& optarg, int optopt); + + //! Handle non-option parameters. + virtual int nonoption(const std::string& argv); + + //! Print a minimal usage note to an output stream. + void usage(std::ostream& os =std::cout) const; + + //! Print further usage explanations to an output stream. + void help(std::ostream& os =std::cout) const; + + //! Print version information to an output stream. + void version(std::ostream& os =std::cout) const; +}; // class Params + +#endif // #ifndef EXIV2_HPP_ diff --git a/src/plugins/exiv2extractor.cc b/src/plugins/exiv2/exiv2extractor.cc diff --git a/src/plugins/exiv2/fujimn.cpp b/src/plugins/exiv2/fujimn.cpp @@ -0,0 +1,278 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: fujimn.cpp + Version: $Rev: 569 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 18-Feb-04, ahu: created + 07-Mar-04, ahu: isolated as a separate component + Credits: Fujifilm MakerNote implemented according to the specification + in "Appendix 4: Makernote of Fujifilm" of the document + "Exif file format" by TsuruZoh Tachibanaya + <http://park2.wakwak.com/%7Etsuruzoh/Computer/Digicams/exif-e.html> + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: fujimn.cpp 569 2005-05-28 05:48:43Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "fujimn.hpp" +#include "makernote.hpp" +#include "value.hpp" + +// + standard includes +#include <string> +#include <sstream> +#include <iomanip> +#include <cassert> + +// Define DEBUG_MAKERNOTE to output debug information to std::cerr +#undef DEBUG_MAKERNOTE + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + //! @cond IGNORE + FujiMakerNote::RegisterMn::RegisterMn() + { + MakerNoteFactory::registerMakerNote("FUJIFILM", "*", createFujiMakerNote); + MakerNoteFactory::registerMakerNote( + fujiIfdId, MakerNote::AutoPtr(new FujiMakerNote)); + + ExifTags::registerMakerTagInfo(fujiIfdId, tagInfo_); + } + //! @endcond + + // Fujifilm MakerNote Tag Info + const TagInfo FujiMakerNote::tagInfo_[] = { + TagInfo(0x0000, "Version", "Fujifilm Makernote version", fujiIfdId, makerTags, undefined, printValue), + TagInfo(0x1000, "Quality", "Image quality setting", fujiIfdId, makerTags, asciiString, printValue), + TagInfo(0x1001, "Sharpness", "Sharpness setting", fujiIfdId, makerTags, unsignedShort, print0x1001), + TagInfo(0x1002, "WhiteBalance", "White balance setting", fujiIfdId, makerTags, unsignedShort, print0x1002), + TagInfo(0x1003, "Color", "Chroma saturation setting", fujiIfdId, makerTags, unsignedShort, print0x1003), + TagInfo(0x1004, "Tone", "Contrast setting", fujiIfdId, makerTags, unsignedShort, print0x1004), + TagInfo(0x1010, "FlashMode", "Flash firing mode setting", fujiIfdId, makerTags, unsignedShort, print0x1010), + TagInfo(0x1011, "FlashStrength", "Flash firing strength compensation setting", fujiIfdId, makerTags, signedRational, printValue), + TagInfo(0x1020, "Macro", "Macro mode setting", fujiIfdId, makerTags, unsignedShort, printOffOn), + TagInfo(0x1021, "FocusMode", "Focusing mode setting", fujiIfdId, makerTags, unsignedShort, print0x1021), + TagInfo(0x1022, "0x1022", "Unknown", fujiIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1030, "SlowSync", "Slow synchro mode setting", fujiIfdId, makerTags, unsignedShort, printOffOn), + TagInfo(0x1031, "PictureMode", "Picture mode setting", fujiIfdId, makerTags, unsignedShort, print0x1031), + TagInfo(0x1032, "0x1032", "Unknown", fujiIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1100, "Continuous", "Continuous shooting or auto bracketing setting", fujiIfdId, makerTags, unsignedShort, printOffOn), + TagInfo(0x1101, "0x1101", "Unknown", fujiIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1200, "0x1200", "Unknown", fujiIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1300, "BlurWarning", "Blur warning status", fujiIfdId, makerTags, unsignedShort, printOffOn), + TagInfo(0x1301, "FocusWarning", "Auto Focus warning status", fujiIfdId, makerTags, unsignedShort, printOffOn), + TagInfo(0x1302, "AeWarning", "Auto Exposure warning status", fujiIfdId, makerTags, unsignedShort, printOffOn), + // End of list marker + TagInfo(0xffff, "(UnknownFujiMakerNoteTag)", "Unknown FujiMakerNote tag", fujiIfdId, makerTags, invalidTypeId, printValue) + }; + + FujiMakerNote::FujiMakerNote(bool alloc) + : IfdMakerNote(fujiIfdId, alloc) + { + byteOrder_ = littleEndian; + absOffset_ = false; + byte buf[] = { + 'F', 'U', 'J', 'I', 'F', 'I', 'L', 'M', 0x0c, 0x00, 0x00, 0x00 + }; + readHeader(buf, 12, byteOrder_); + } + + FujiMakerNote::FujiMakerNote(const FujiMakerNote& rhs) + : IfdMakerNote(rhs) + { + } + + int FujiMakerNote::readHeader(const byte* buf, + long len, + ByteOrder byteOrder) + { + if (len < 12) return 1; + + header_.alloc(12); + memcpy(header_.pData_, buf, header_.size_); + // Read the offset relative to the start of the makernote from the header + // Note: we ignore the byteOrder paramter + adjOffset_ = getUShort(header_.pData_ + 8, byteOrder_); + return 0; + } + + int FujiMakerNote::checkHeader() const + { + int rc = 0; + // Check the FUJIFILM prefix + if ( header_.size_ < 12 + || std::string(reinterpret_cast<char*>(header_.pData_), 8) + != std::string("FUJIFILM", 8)) { + rc = 2; + } + return rc; + } + + FujiMakerNote::AutoPtr FujiMakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + FujiMakerNote* FujiMakerNote::create_(bool alloc) const + { + AutoPtr makerNote = AutoPtr(new FujiMakerNote(alloc)); + assert(makerNote.get() != 0); + makerNote->readHeader(header_.pData_, header_.size_, byteOrder_); + return makerNote.release(); + } + + FujiMakerNote::AutoPtr FujiMakerNote::clone() const + { + return AutoPtr(clone_()); + } + + FujiMakerNote* FujiMakerNote::clone_() const + { + return new FujiMakerNote(*this); + } + + std::ostream& FujiMakerNote::printOffOn(std::ostream& os, + const Value& value) + { + switch (value.toLong()) { + case 0: os << "Off"; break; + case 1: os << "On"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& FujiMakerNote::print0x1001(std::ostream& os, + const Value& value) + { + switch (value.toLong()) { + case 1: // fallthrough + case 2: os << "Soft"; break; + case 3: os << "Normal"; break; + case 4: // fallthrough + case 5: os << "Hard"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& FujiMakerNote::print0x1002(std::ostream& os, + const Value& value) + { + switch (value.toLong()) { + case 0: os << "Auto"; break; + case 256: os << "Daylight"; break; + case 512: os << "Cloudy"; break; + case 768: os << "Fluorescent (daylight)"; break; + case 769: os << "Fluorescent (warm white)"; break; + case 770: os << "Fluorescent (cool white)"; break; + case 1024: os << "Incandescent"; break; + case 3480: os << "Custom"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& FujiMakerNote::print0x1003(std::ostream& os, + const Value& value) + { + switch (value.toLong()) { + case 0: os << "Standard"; break; + case 256: os << "High"; break; + case 512: os << "Original"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& FujiMakerNote::print0x1004(std::ostream& os, + const Value& value) + { + switch (value.toLong()) { + case 0: os << "Standard"; break; + case 256: os << "Hard"; break; + case 512: os << "Original"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& FujiMakerNote::print0x1010(std::ostream& os, + const Value& value) + { + switch (value.toLong()) { + case 0: os << "Auto"; break; + case 1: os << "On"; break; + case 2: os << "Off"; break; + case 3: os << "Red-eye"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& FujiMakerNote::print0x1021(std::ostream& os, + const Value& value) + { + switch (value.toLong()) { + case 0: os << "Auto"; break; + case 1: os << "Manual"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& FujiMakerNote::print0x1031(std::ostream& os, + const Value& value) + { + switch (value.toLong()) { + case 0: os << "Auto"; break; + case 1: os << "Portrait"; break; + case 2: os << "Landscape"; break; + case 4: os << "Sports"; break; + case 5: os << "Night"; break; + case 6: os << "Program"; break; + case 256: os << "Aperture priority"; break; + case 512: os << "Shutter priority"; break; + case 768: os << "Manual"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + +// ***************************************************************************** +// free functions + + MakerNote::AutoPtr createFujiMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset) + { + return MakerNote::AutoPtr(new FujiMakerNote(alloc)); + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/fujimn.hpp b/src/plugins/exiv2/fujimn.hpp @@ -0,0 +1,162 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file fujimn.hpp + @brief Fujifilm MakerNote implemented according to the specification + in Appendix 4: Makernote of Fujifilm of the document + <a href="http://park2.wakwak.com/%7Etsuruzoh/Computer/Digicams/exif-e.html"> + Exif file format</a> by TsuruZoh Tachibanaya + @version $Rev: 569 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 11-Feb-04, ahu: created + */ +#ifndef FUJIMN_HPP_ +#define FUJIMN_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "makernote.hpp" +#include "tags.hpp" + +// + standard includes +#include <string> +#include <iosfwd> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class Value; + +// ***************************************************************************** +// free functions + + /*! + @brief Return an auto-pointer to a newly created empty MakerNote + initialized to operate in the memory management model indicated. + The caller owns this copy and the auto-pointer ensures that it + will be deleted. + + @param alloc Memory management model for the new MakerNote. Determines if + memory required to store data should be allocated and deallocated + (true) or not (false). If false, only pointers to the buffer + provided to read() will be kept. See Ifd for more background on + this concept. + @param buf Pointer to the makernote character buffer (not used). + @param len Length of the makernote character buffer (not used). + @param byteOrder Byte order in which the Exif data (and possibly the + makernote) is encoded (not used). + @param offset Offset from the start of the TIFF header of the makernote + buffer (not used). + + @return An auto-pointer to a newly created empty MakerNote. The caller + owns this copy and the auto-pointer ensures that it will be + deleted. + */ + MakerNote::AutoPtr createFujiMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset); + +// ***************************************************************************** +// class definitions + + //! MakerNote for Fujifilm cameras + class FujiMakerNote : public IfdMakerNote { + public: + //! Shortcut for a %FujiMakerNote auto pointer. + typedef std::auto_ptr<FujiMakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to choose whether or not memory management + is required for the makernote entries. + */ + FujiMakerNote(bool alloc =true); + //! Copy constructor + FujiMakerNote(const FujiMakerNote& rhs); + //! Virtual destructor + virtual ~FujiMakerNote() {} + //@} + + //! @name Manipulators + //@{ + int readHeader(const byte* buf, + long len, + ByteOrder byteOrder); + //@} + + //! @name Accessors + //@{ + int checkHeader() const; + AutoPtr create(bool alloc =true) const; + AutoPtr clone() const; + //@} + + //! @name Print functions for Fujifilm %MakerNote tags + //@{ + //! Print Off or On status + static std::ostream& printOffOn(std::ostream& os, const Value& value); + //! Print sharpness + static std::ostream& print0x1001(std::ostream& os, const Value& value); + //! Print white balance + static std::ostream& print0x1002(std::ostream& os, const Value& value); + //! Print color + static std::ostream& print0x1003(std::ostream& os, const Value& value); + //! Print tone + static std::ostream& print0x1004(std::ostream& os, const Value& value); + //! Print flash mode + static std::ostream& print0x1010(std::ostream& os, const Value& value); + //! Print focus mode + static std::ostream& print0x1021(std::ostream& os, const Value& value); + //! Print picture mode + static std::ostream& print0x1031(std::ostream& os, const Value& value); + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct RegisterMn { + RegisterMn(); + }; + //! @endcond + + private: + //! Internal virtual create function. + FujiMakerNote* create_(bool alloc =true) const; + //! Internal virtual copy constructor. + FujiMakerNote* clone_() const; + + //! Tag information + static const TagInfo tagInfo_[]; + + }; // class FujiMakerNote + + static FujiMakerNote::RegisterMn registerFujiMakerNote; +} // namespace Exiv2 + +#endif // #ifndef FUJIMN_HPP_ diff --git a/src/plugins/exiv2/futils.cpp b/src/plugins/exiv2/futils.cpp @@ -0,0 +1,78 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: utils.cpp + Version: $Rev: 560 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 08-Dec-03, ahu: created + 02-Apr-05, ahu: moved to Exiv2 namespace + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: futils.cpp 560 2005-04-17 11:51:32Z ahuggel $"); + +// ***************************************************************************** +// included header files +#ifdef _MSC_VER +# include "exv_msvc.h" +#else +# include "exv_conf.h" +#endif + +#include "futils.hpp" + +// + standard includes +#include <sys/types.h> +#include <sys/stat.h> +#ifdef _MSC_VER +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif +#ifdef EXV_HAVE_UNISTD_H +# include <unistd.h> // for stat() +#endif + +#include <cerrno> +#include <cstring> +#include <sstream> + +namespace Exiv2 { + +// ***************************************************************************** +// free functions + + bool fileExists(const std::string& path, bool ct) + { + struct stat buf; + int ret = stat(path.c_str(), &buf); + if (0 != ret) return false; + if (ct && !S_ISREG(buf.st_mode)) return false; + return true; + } // fileExists + + std::string strError() + { + int error = errno; + std::ostringstream os; + os << strerror(error) << " (" << error << ")"; + return os.str(); + } // strError + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/futils.hpp b/src/plugins/exiv2/futils.hpp @@ -0,0 +1,66 @@ +// ********************************************************* -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file futils.hpp + @brief Basic file utility functions required by Exiv2 + @version $Rev: 560 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 12-Dec-03, ahu: created<BR> + 02-Apr-05, ahu: moved to Exiv2 namespace + */ +#ifndef FUTILS_HPP_ +#define FUTILS_HPP_ + +// ********************************************************************* +// included header files +// + standard includes +#include <string> + +// ********************************************************************* +// namespace extensions +namespace Exiv2 { + +// ********************************************************************* +// free functions + + /*! + @brief Test if a file exists. + + @param path Name of file to verify. + @param ct Flag to check if <i>path</i> is a regular file. + @return true if <i>path</i> exists and, if <i>ct</i> is set, + is a regular file, else false. + + @note The function calls <b>stat()</b> test for <i>path</i> + and its type, see stat(2). <b>errno</b> is left unchanged + in case of an error. + */ + bool fileExists(const std::string& path, bool ct =false); + /*! + @brief Return a system error message and the error code (errno). + See %strerror(3). + */ + std::string strError(); + +} // namespace Exiv2 + +#endif // #ifndef FUTILS_HPP_ diff --git a/src/plugins/exiv2/ifd.cpp b/src/plugins/exiv2/ifd.cpp @@ -0,0 +1,719 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: ifd.cpp + Version: $Rev: 562 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 26-Jan-04, ahu: created + 11-Feb-04, ahu: isolated as a component + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: ifd.cpp 562 2005-04-20 18:16:29Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "ifd.hpp" +#include "types.hpp" +#include "error.hpp" +#include "tags.hpp" // for ExifTags::ifdName + +// + standard includes +#include <iostream> +#include <iomanip> +#include <sstream> +#include <vector> +#include <algorithm> +#include <cstring> +#include <cassert> + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + Entry::Entry(bool alloc) + : alloc_(alloc), ifdId_(ifdIdNotSet), idx_(0), + tag_(0), type_(0), count_(0), offset_(0), size_(0), pData_(0), + sizeDataArea_(0), pDataArea_(0) + { + } + + Entry::~Entry() + { + if (alloc_) { + delete[] pData_; + delete[] pDataArea_; + } + } + + Entry::Entry(const Entry& rhs) + : alloc_(rhs.alloc_), ifdId_(rhs.ifdId_), idx_(rhs.idx_), + tag_(rhs.tag_), type_(rhs.type_), + count_(rhs.count_), offset_(rhs.offset_), size_(rhs.size_), pData_(0), + sizeDataArea_(rhs.sizeDataArea_), pDataArea_(0) + { + if (alloc_) { + if (rhs.pData_) { + pData_ = new byte[rhs.size()]; + memcpy(pData_, rhs.pData_, rhs.size()); + } + if (rhs.pDataArea_) { + pDataArea_ = new byte[rhs.sizeDataArea()]; + memcpy(pDataArea_, rhs.pDataArea_, rhs.sizeDataArea()); + } + } + else { + pData_ = rhs.pData_; + pDataArea_ = rhs.pDataArea_; + } + } + + Entry& Entry::operator=(const Entry& rhs) + { + if (this == &rhs) return *this; + alloc_ = rhs.alloc_; + ifdId_ = rhs.ifdId_; + idx_ = rhs.idx_; + tag_ = rhs.tag_; + type_ = rhs.type_; + count_ = rhs.count_; + offset_ = rhs.offset_; + size_ = rhs.size_; + sizeDataArea_ = rhs.sizeDataArea_; + if (alloc_) { + delete[] pData_; + pData_ = 0; + if (rhs.pData_) { + pData_ = new byte[rhs.size()]; + memcpy(pData_, rhs.pData_, rhs.size()); + } + delete[] pDataArea_; + pDataArea_ = 0; + if (rhs.pDataArea_) { + pDataArea_ = new byte[rhs.sizeDataArea()]; + memcpy(pDataArea_, rhs.pDataArea_, rhs.sizeDataArea()); + } + } + else { + pData_ = rhs.pData_; + pDataArea_ = rhs.pDataArea_; + } + return *this; + } // Entry::operator= + + void Entry::setValue(uint32_t data, ByteOrder byteOrder) + { + if (pData_ == 0 || size_ < 4) { + assert(alloc_); + size_ = 4; + delete[] pData_; + pData_ = new byte[size_]; + } + ul2Data(pData_, data, byteOrder); + // do not change size_ + type_ = unsignedLong; + count_ = 1; + } + + void Entry::setValue(uint16_t type, uint32_t count, const byte* buf, long len) + { + long dataSize = count * TypeInfo::typeSize(TypeId(type)); + // No minimum size requirement, but make sure the buffer can hold the data + if (len < dataSize) throw Error(24, tag(), dataSize, len); + if (alloc_) { + delete[] pData_; + pData_ = new byte[len]; + memset(pData_, 0x0, len); + memcpy(pData_, buf, dataSize); + size_ = len; + } + else { + if (size_ == 0) { + // Set the data pointer of a virgin entry + pData_ = const_cast<byte*>(buf); + size_ = len; + } + else { + // Overwrite existing data if it fits into the buffer + if (size_ < dataSize) throw Error(24, tag(), dataSize, size_); + memset(pData_, 0x0, size_); + memcpy(pData_, buf, dataSize); + // do not change size_ + } + } + type_ = type; + count_ = count; + } // Entry::setValue + + void Entry::setDataArea(const byte* buf, long len) + { + if (alloc_) { + delete[] pDataArea_; + pDataArea_ = new byte[len]; + memcpy(pDataArea_, buf, len); + sizeDataArea_ = len; + } + else { + if (sizeDataArea_ == 0) { + // Set the data area pointer of a virgin entry + pDataArea_ = const_cast<byte*>(buf); + sizeDataArea_ = len; + } + else { + // Overwrite existing data if it fits into the buffer + if (sizeDataArea_ < len) { + throw Error(25, tag(), sizeDataArea_, len); + } + memset(pDataArea_, 0x0, sizeDataArea_); + memcpy(pDataArea_, buf, len); + // do not change sizeDataArea_ + } + } + } // Entry::setDataArea + + void Entry::setDataAreaOffsets(uint32_t offset, ByteOrder byteOrder) + { + for (uint32_t i = 0; i < count(); ++i) { + byte* buf = pData_ + i * typeSize(); + switch(TypeId(type())) { + case unsignedShort: { + uint16_t d = getUShort(buf, byteOrder); + if (d + offset > 0xffff) throw Error(26); + us2Data(buf, d + static_cast<uint16_t>(offset), byteOrder); + break; + } + case unsignedLong: { + ul2Data(buf, getULong(buf, byteOrder) + offset, byteOrder); + break; + } + case unsignedRational: { + URational d = getURational(buf, byteOrder); + d.first = d.first + offset * d.second; + ur2Data(buf, d, byteOrder); + break; + } + case signedShort: { + int16_t d = getShort(buf, byteOrder); + if (d + static_cast<int32_t>(offset) > 0xffff) throw Error(26); + s2Data(buf, d + static_cast<int16_t>(offset), byteOrder); + break; + } + case signedLong: { + int32_t d = getLong(buf, byteOrder); + l2Data(buf, d + static_cast<int32_t>(offset), byteOrder); + break; + } + case signedRational: { + Rational d = getRational(buf, byteOrder); + d.first = d.first + static_cast<int32_t>(offset) * d.second; + r2Data(buf, d, byteOrder); + break; + } + default: + throw Error(27); + break; + } + } + } // Entry::setDataAreaOffsets + + void Entry::updateBase(byte* pOldBase, byte* pNewBase) + { + if (!alloc_) { + if (pDataArea_) { + pDataArea_ = pDataArea_ - pOldBase + pNewBase; + } + if (pData_) { + pData_ = pData_ - pOldBase + pNewBase; + } + } + } // Entry::updateBase + + const byte* Entry::component(uint32_t n) const + { + if (n >= count()) return 0; + return data() + n * typeSize(); + } // Entry::component + + Ifd::Ifd(IfdId ifdId) + : alloc_(true), ifdId_(ifdId), pBase_(0), offset_(0), + dataOffset_(0), hasNext_(true), pNext_(0), next_(0) + { + pNext_ = new byte[4]; + memset(pNext_, 0x0, 4); + } + + Ifd::Ifd(IfdId ifdId, long offset) + : alloc_(true), ifdId_(ifdId), pBase_(0), offset_(offset), + dataOffset_(0), hasNext_(true), pNext_(0), next_(0) + { + pNext_ = new byte[4]; + memset(pNext_, 0x0, 4); + } + + Ifd::Ifd(IfdId ifdId, long offset, bool alloc, bool hasNext) + : alloc_(alloc), ifdId_(ifdId), pBase_(0), offset_(offset), + dataOffset_(0), hasNext_(hasNext), pNext_(0), next_(0) + { + if (alloc_ && hasNext_) { + pNext_ = new byte[4]; + memset(pNext_, 0x0, 4); + } + } + + Ifd::~Ifd() + { + // do not delete pBase_ + if (alloc_ && hasNext_) delete[] pNext_; + } + + Ifd::Ifd(const Ifd& rhs) + : alloc_(rhs.alloc_), entries_(rhs.entries_), ifdId_(rhs.ifdId_), + pBase_(rhs.pBase_), offset_(rhs.offset_), dataOffset_(rhs.dataOffset_), + hasNext_(rhs.hasNext_), pNext_(rhs.pNext_), next_(rhs.next_) + { + if (alloc_ && hasNext_) { + pNext_ = new byte[4]; + memset(pNext_, 0x0, 4); + if (rhs.pNext_) memcpy(pNext_, rhs.pNext_, 4); + } + } + + int Ifd::read(const byte* buf, long len, ByteOrder byteOrder, long offset) + { + // Todo: This is a hack to work around bug #424 - fix it properly! + if (ifdId_ == olympusIfdId) len = 65535; + + int rc = 0; + long o = 0; + Ifd::PreEntries preEntries; + + if (len < 2) rc = 6; + if (rc == 0) { + offset_ = offset; + int n = getUShort(buf, byteOrder); + o = 2; + + for (int i = 0; i < n; ++i) { + if (len < o + 12) { + // Todo: How to handle debug output like this + std::cerr << "Error: " << ExifTags::ifdName(ifdId_) + << " entry " << i + << " lies outside of the IFD memory buffer.\n"; + rc = 6; + break; + } + Ifd::PreEntry pe; + pe.tag_ = getUShort(buf + o, byteOrder); + pe.type_ = getUShort(buf + o + 2, byteOrder); + pe.count_ = getULong(buf + o + 4, byteOrder); + pe.size_ = pe.count_ * TypeInfo::typeSize(TypeId(pe.type_)); + pe.offsetLoc_ = o + 8; + pe.offset_ = pe.size_ > 4 ? getLong(buf + o + 8, byteOrder) : 0; + preEntries.push_back(pe); + o += 12; + } + } + if (rc == 0 && hasNext_) { + if (len < o + 4) { + // Todo: How to handle debug output like this + std::cerr << "Error: " << ExifTags::ifdName(ifdId_) + << " memory of the pointer to the next IFD" + << " lies outside of the IFD memory buffer.\n"; + rc = 6; + } + else { + if (alloc_) { + memcpy(pNext_, buf + o, 4); + } + else { + pNext_ = const_cast<byte*>(buf + o); + } + next_ = getULong(buf + o, byteOrder); + } + } + // Set the offset of the first data entry outside of the IFD. + // At the same time we guess the offset of the IFD, if it was not + // given. The guess is based on the assumption that the smallest offset + // points to a data buffer directly following the IFD. Subsequently all + // offsets of IFD entries will need to be recalculated. + if (rc == 0 && preEntries.size() > 0) { + // Find the entry with the smallest offset + Ifd::PreEntries::const_iterator i = std::min_element( + preEntries.begin(), preEntries.end(), cmpPreEntriesByOffset); + // Only do something if there is at least one entry with data + // outside the IFD directory itself. + if (i->size_ > 4) { + if (offset_ == 0) { + // Set the 'guessed' IFD offset + offset_ = i->offset_ + - (2 + 12 * static_cast<long>(preEntries.size()) + + (hasNext_ ? 4 : 0)); + } + // Set the offset of the first data entry outside of the IFD + if (i->offset_ - offset_ >= len) { + // Todo: How to handle debug output like this + std::cerr << "Error: Offset of the 1st data entry of " + << ExifTags::ifdName(ifdId_) + << " is out of bounds:\n" + << " Offset = 0x" << std::setw(8) + << std::setfill('0') << std::hex + << i->offset_ - offset_ + << ", exceeds buffer size by " + << std::dec << i->offset_ - len + << " Bytes\n"; + rc = 6; + } + else { + dataOffset_ = i->offset_; + } + } + } + // Convert the pre-IFD entries to the actual entries, assign the data + // to each IFD entry and calculate relative offsets, relative to the + // start of the IFD + if (rc == 0) { + entries_.clear(); + int idx = 0; + const Ifd::PreEntries::iterator begin = preEntries.begin(); + const Ifd::PreEntries::iterator end = preEntries.end(); + for (Ifd::PreEntries::iterator i = begin; i != end; ++i) { + Entry e(alloc_); + e.setIfdId(ifdId_); + e.setIdx(++idx); + e.setTag(i->tag_); + long tmpOffset = + i->size_ > 4 ? i->offset_ - offset_ : i->offsetLoc_; + if (tmpOffset + i->size_ > len) { + // Todo: How to handle debug output like this + std::cerr << "Warning: Upper boundary of data for " + << ExifTags::ifdName(ifdId_) + << " entry " << static_cast<int>(i - begin) + << " is out of bounds:\n" + << " Offset = 0x" << std::setw(8) + << std::setfill('0') << std::hex + << tmpOffset + << ", size = " << std::dec << i->size_ + << ", exceeds buffer size by " + << tmpOffset + i->size_ - len + << " Bytes; Truncating the data.\n"; + // Truncate the entry + i->size_ = 0; + i->count_ = 0; + tmpOffset = i->offsetLoc_; + } + // Set the offset to the data, relative to start of IFD + e.setOffset(tmpOffset); + // Set the size to at least for bytes to accomodate offset-data + e.setValue(i->type_, i->count_, buf + e.offset(), + std::max(long(4), i->size_)); + this->add(e); + } + } + if (!alloc_) pBase_ = const_cast<byte*>(buf) - offset_; + if (rc) this->clear(); + + return rc; + } // Ifd::read + + Ifd::const_iterator Ifd::findIdx(int idx) const + { + return std::find_if(entries_.begin(), entries_.end(), + FindEntryByIdx(idx)); + } + + Ifd::iterator Ifd::findIdx(int idx) + { + return std::find_if(entries_.begin(), entries_.end(), + FindEntryByIdx(idx)); + } + + Ifd::const_iterator Ifd::findTag(uint16_t tag) const + { + return std::find_if(entries_.begin(), entries_.end(), + FindEntryByTag(tag)); + } + + Ifd::iterator Ifd::findTag(uint16_t tag) + { + return std::find_if(entries_.begin(), entries_.end(), + FindEntryByTag(tag)); + } + + void Ifd::sortByTag() + { + std::sort(entries_.begin(), entries_.end(), cmpEntriesByTag); + } + + int Ifd::readSubIfd( + Ifd& dest, const byte* buf, long len, ByteOrder byteOrder, uint16_t tag + ) const + { + int rc = 0; + const_iterator pos = findTag(tag); + if (pos != entries_.end()) { + long offset = getULong(pos->data(), byteOrder); + if (len < offset) { + rc = 6; + } + else { + rc = dest.read(buf + offset, len - offset, byteOrder, offset); + } + } + return rc; + } // Ifd::readSubIfd + + long Ifd::copy(byte* buf, ByteOrder byteOrder, long offset) + { + if (entries_.size() == 0 && next_ == 0) return 0; + if (offset != 0) offset_ = offset; + + // Add the number of entries to the data buffer + us2Data(buf, static_cast<uint16_t>(entries_.size()), byteOrder); + long o = 2; + + // Add all directory entries to the data buffer + long dataSize = 0; + long dataAreaSize = 0; + long totalDataSize = 0; + const iterator b = entries_.begin(); + const iterator e = entries_.end(); + iterator i; + for (i = b; i != e; ++i) { + if (i->size() > 4) { + totalDataSize += i->size(); + } + } + for (i = b; i != e; ++i) { + us2Data(buf + o, i->tag(), byteOrder); + us2Data(buf + o + 2, i->type(), byteOrder); + ul2Data(buf + o + 4, i->count(), byteOrder); + if (i->sizeDataArea() > 0) { + long dataAreaOffset = offset_+size()+totalDataSize+dataAreaSize; + i->setDataAreaOffsets(dataAreaOffset, byteOrder); + dataAreaSize += i->sizeDataArea(); + } + if (i->size() > 4) { + // Set the offset of the entry, data immediately follows the IFD + i->setOffset(size() + dataSize); + l2Data(buf + o + 8, offset_ + i->offset(), byteOrder); + dataSize += i->size(); + } + else { + // Copy data into the offset field + memset(buf + o + 8, 0x0, 4); + memcpy(buf + o + 8, i->data(), i->size()); + } + o += 12; + } + + if (hasNext_) { + // Add the offset to the next IFD to the data buffer + if (pNext_) { + memcpy(buf + o, pNext_, 4); + } + else { + memset(buf + o, 0x0, 4); + } + o += 4; + } + + // Add the data of all IFD entries to the data buffer + for (i = b; i != e; ++i) { + if (i->size() > 4) { + memcpy(buf + o, i->data(), i->size()); + o += i->size(); + } + } + + // Add all data areas to the data buffer + for (i = b; i != e; ++i) { + if (i->sizeDataArea() > 0) { + memcpy(buf + o, i->dataArea(), i->sizeDataArea()); + o += i->sizeDataArea(); + } + } + + return o; + } // Ifd::copy + + void Ifd::clear() + { + entries_.clear(); + offset_ = 0; + dataOffset_ = 0; + if (hasNext_) { + if (alloc_) { + memset(pNext_, 0x0, 4); + } + else { + pBase_ = 0; + pNext_ = 0; + } + next_ = 0; + } + } // Ifd::clear + + void Ifd::setNext(uint32_t next, ByteOrder byteOrder) + { + if (hasNext_) { + assert(pNext_); + ul2Data(pNext_, next, byteOrder); + next_ = next; + } + } + + void Ifd::add(const Entry& entry) + { + assert(alloc_ == entry.alloc()); + assert(ifdId_ == entry.ifdId()); + // allow duplicates + entries_.push_back(entry); + } + + int Ifd::erase(uint16_t tag) + { + int idx = 0; + iterator pos = findTag(tag); + if (pos != end()) { + idx = pos->idx(); + erase(pos); + } + return idx; + } + + Ifd::iterator Ifd::erase(iterator pos) + { + return entries_.erase(pos); + } + + byte* Ifd::updateBase(byte* pNewBase) + { + byte *pOld = 0; + if (!alloc_) { + iterator end = this->end(); + for (iterator pos = begin(); pos != end; ++pos) { + pos->updateBase(pBase_, pNewBase); + } + if (hasNext_) { + pNext_ = pNext_ - pBase_ + pNewBase; + } + pOld = pBase_; + pBase_ = pNewBase; + } + return pOld; + } + + long Ifd::size() const + { + if (entries_.size() == 0 && next_ == 0) return 0; + return static_cast<long>(2 + 12 * entries_.size() + (hasNext_ ? 4 : 0)); + } + + long Ifd::dataSize() const + { + long dataSize = 0; + const_iterator end = this->end(); + for (const_iterator i = begin(); i != end; ++i) { + if (i->size() > 4) dataSize += i->size(); + dataSize += i->sizeDataArea(); + } + return dataSize; + } + + void Ifd::print(std::ostream& os, const std::string& prefix) const + { + if (entries_.size() == 0) return; + // Print a header + os << prefix << "IFD Offset: 0x" + << std::setw(8) << std::setfill('0') << std::hex << std::right + << offset_ + << ", IFD Entries: " + << std::setfill(' ') << std::dec << std::right + << static_cast<unsigned int>(entries_.size()) << "\n" + << prefix << "Entry Tag Format (Bytes each) Number Offset\n" + << prefix << "----- ------ --------------------- ------ -----------\n"; + // Print IFD entries + const const_iterator b = entries_.begin(); + const const_iterator e = entries_.end(); + const_iterator i = b; + for (; i != e; ++i) { + std::ostringstream offset; + if (i->size() > 4) { + offset << " 0x" << std::setw(8) << std::setfill('0') + << std::hex << std::right << i->offset(); + } + else { + const byte* data = i->data(); + for (int k = 0; k < i->size(); ++k) { + offset << std::setw(2) << std::setfill('0') << std::hex + << (int)data[k] << " "; + } + } + os << prefix << std::setw(5) << std::setfill(' ') << std::dec + << std::right << static_cast<int>(i - b) + << " 0x" << std::setw(4) << std::setfill('0') << std::hex + << std::right << i->tag() + << " " << std::setw(17) << std::setfill(' ') + << std::left << i->typeName() + << " (" << std::dec << i->typeSize() << ")" + << " " << std::setw(6) << std::setfill(' ') << std::dec + << std::right << i->count() + << " " << offset.str() + << "\n"; + } + if (hasNext_) { + os << prefix << "Next IFD: 0x" + << std::setw(8) << std::setfill('0') << std::hex + << std::right << next() << "\n"; + } + // Print data of IFD entries + for (i = b; i != e; ++i) { + if (i->size() > 4) { + os << "Data of entry " << static_cast<int>(i - b) << ":\n"; + hexdump(os, i->data(), i->size(), offset_ + i->offset()); + } + } + + } // Ifd::print + + // ************************************************************************* + // free functions + + bool cmpEntriesByTag(const Entry& lhs, const Entry& rhs) + { + return lhs.tag() < rhs.tag(); + } + + bool cmpPreEntriesByOffset(const Ifd::PreEntry& lhs, const Ifd::PreEntry& rhs) + { + // We need to ignore entries with size <= 4, so by definition, + // entries with size <= 4 are greater than those with size > 4 + // when compared by their offset. + if (lhs.size_ <= 4) { + return false; // lhs is greater by definition, or they are equal + } + if (rhs.size_ <= 4) { + return true; // rhs is greater by definition (they cannot be equal) + } + return lhs.offset_ < rhs.offset_; + } // cmpPreEntriesByOffset + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/ifd.hpp b/src/plugins/exiv2/ifd.hpp @@ -0,0 +1,601 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file ifd.hpp + @brief Encoding and decoding of IFD (%Image File Directory) data + @version $Rev: 562 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 09-Jan-04, ahu: created<BR> + 11-Feb-04, ahu: isolated as a component + */ +#ifndef IFD_HPP_ +#define IFD_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" + +// + standard includes +#include <string> +#include <vector> +#include <iosfwd> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class Ifd; + +// ***************************************************************************** +// class definitions + + /*! + @brief Data structure for one IFD directory entry. See the description of + class Ifd for an explanation of the supported modes for memory + allocation. + */ + class Entry { + public: + //! @name Creators + //@{ + /*! + @brief Default constructor. The entry allocates memory for its + data if alloc is true (the default), otherwise it remembers + just the pointers into a read and writeable data buffer which + it doesn't allocate or delete. + */ + explicit Entry(bool alloc =true); + //! Destructor + ~Entry(); + //! Copy constructor + Entry(const Entry& rhs); + //@} + + //! @name Manipulators + //@{ + //! Assignment operator + Entry& operator=(const Entry& rhs); + //! Set the tag + void setTag(uint16_t tag) { tag_ = tag; } + //! Set the IFD id + void setIfdId(IfdId ifdId) { ifdId_ = ifdId; } + //! Set the index (unique id of an entry within one IFD) + void setIdx(int idx) { idx_ = idx; } + //! Set the offset. The offset is relative to the start of the IFD. + void setOffset(long offset) { offset_ = offset; } + /*! + @brief Set the value of the entry to a single unsigned long component, + i.e., set the type of the entry to unsigned long, number of + components to one and the value according to the data provided. + + The size of the data buffer is set to at least four bytes, but is left + unchanged if it can accomodate the pointer. This method can be used + to set the value of a tag which contains a pointer (offset) to a + location in the Exif data (like e.g., ExifTag, 0x8769 in IFD0, which + contains a pointer to the Exif IFD). + <BR>This method cannot be used to set the value of a newly created + %Entry in non-alloc mode. + + @note This method is now deprecated, use data area related methods + instead. + */ + void setValue(uint32_t data, ByteOrder byteOrder); + /*! + @brief Set type, count, the data buffer and its size. + + Copies the provided buffer when called in memory allocation mode. + <BR>In non-alloc mode, use this method to initialise the data of a + newly created %Entry. In this case, only the pointer to the buffer is + copied, i.e., the buffer must remain valid throughout the life of the + %Entry. Subsequent calls in non-alloc mode will overwrite the data + pointed to by this pointer with the data provided, i.e., the buffer + provided in subsequent calls can be deleted after the call. + <BR>In either memory allocation mode, the data buffer provided must be + large enough to hold count components of type. The size of the buffer + will be as indicated in the size argument. I.e., it is possible to + allocate (set) a data buffer larger than required to hold count + components of the given type. + + @param type The type of the data. + @param count Number of components in the buffer. + @param data Pointer to the data buffer. + @param size Size of the desired data buffer in bytes. + @throw Error if no memory allocation is allowed + and the size of the data buffer is larger than the existing + data buffer of the entry or if size is not large enough to hold + count components of the given type. + */ + void setValue(uint16_t type, uint32_t count, const byte* data, long size); + /*! + @brief Set the data area. Memory management as for + setValue(uint16_t, uint32_t, const byte*, long) + + For certain tags the regular value of an IFD entry is an offset to a + data area outside of the IFD. Examples are Exif tag 0x8769 in IFD0 + (Exif.Image.ExifTag) or tag 0x0201 in IFD1 + (Exif.Thumbnail.JPEGInterchangeFormat). The offset of ExifTag points + to a data area containing the Exif IFD. That of JPEGInterchangeFormat + contains the JPEG thumbnail image. + This method sets the data area of a tag in accordance with the memory + allocation mode. + + @param buf Pointer to the data area. + @param len Size of the data area. + + @throw Error in non-alloc mode, if there already is a dataarea but the + size of the existing dataarea is not large enough for the + new buffer. + */ + void setDataArea(const byte* buf, long len); + /*! + @brief Set the offset(s) to the data area of an entry. + + Add @em offset to each data component of the entry. This is used by + Ifd::copy to convert the data components of an entry containing + offsets relative to the data area to become offsets from the start of + the TIFF header. Usually, entries with a data area have exactly one + unsigned long data component, which is 0. + + @param offset Offset + @param byteOrder Byte order + + @throw Error if the offset is out of range for the data type of the + tag or the data type is not supported. + */ + void setDataAreaOffsets(uint32_t offset, ByteOrder byteOrder); + /*! + @brief Update the base pointer of the Entry from \em pOldBase + to \em pNewBase. + + Allows to re-locate the underlying data buffer to a new location + \em pNewBase. This method only has an effect in non-alloc mode. + + @param pOldBase Base pointer of the old data buffer + @param pNewBase Base pointer of the new data buffer + */ + void updateBase(byte* pOldBase, byte* pNewBase); + //@} + + //! @name Accessors + //@{ + //! Return the tag + uint16_t tag() const { return tag_; } + //! Return the type id. + uint16_t type() const { return type_; } + //! Return the name of the type + const char* typeName() const + { return TypeInfo::typeName(TypeId(type_)); } + //! Return the size in bytes of one element of this type + long typeSize() const + { return TypeInfo::typeSize(TypeId(type_)); } + //! Return the IFD id + IfdId ifdId() const { return ifdId_; } + //! Return the index (unique id >0 of an entry within an IFD, 0 if not set) + int idx() const { return idx_; } + //! Return the number of components in the value + uint32_t count() const { return count_; } + /*! + @brief Return the size of the data buffer in bytes. + @note There is no minimum size for the data buffer, except that it + must be large enough to hold the data. + */ + long size() const { return size_; } + //! Return the offset from the start of the IFD to the data of the entry + long offset() const { return offset_; } + /*! + @brief Return a pointer to the data buffer. Do not attempt to write + to this pointer. + */ + const byte* data() const { return pData_; } + /*! + @brief Return a pointer to the n-th component, 0 if there is no + n-th component. Do not attempt to write to this pointer. + */ + const byte* component(uint32_t n) const; + //! Get the memory allocation mode + bool alloc() const { return alloc_; } + //! Return the size of the data area. + long sizeDataArea() const { return sizeDataArea_; } + /*! + @brief Return a pointer to the data area. Do not attempt to write to + this pointer. + + For certain tags the regular value of an IFD entry is an offset to a + data area outside of the IFD. Examples are Exif tag 0x8769 in IFD0 + (Exif.Image.ExifTag) or tag 0x0201 in IFD1 + (Exif.Thumbnail.JPEGInterchangeFormat). The offset of ExifTag points + to a data area containing the Exif IFD. That of JPEGInterchangeFormat + contains the JPEG thumbnail image. + Use this method to access (read-only) the data area of a tag. Use + setDataArea() to write to the data area. + + @return Return a pointer to the data area. + */ + const byte* dataArea() const { return pDataArea_; } + //@} + + private: + // DATA + /*! + True: Requires memory allocation and deallocation,<BR> + False: No memory management needed. + */ + bool alloc_; + //! Redundant IFD id (it is also at the IFD) + IfdId ifdId_; + //! Unique id of an entry within an IFD (0 if not set) + int idx_; + //! Tag + uint16_t tag_; + //! Type + uint16_t type_; + //! Number of components + uint32_t count_; + //! Offset from the start of the IFD to the data + long offset_; + /*! + Size of the data buffer holding the value in bytes, there is + no minimum size. + */ + long size_; + //! Pointer to the data buffer + byte* pData_; + //! Size of the data area + long sizeDataArea_; + //! Pointer to the data area + byte* pDataArea_; + + }; // class Entry + + //! Container type to hold all IFD directory entries + typedef std::vector<Entry> Entries; + + //! Unary predicate that matches an Entry with a given index + class FindEntryByIdx { + public: + //! Constructor, initializes the object with the index to look for + FindEntryByIdx(int idx) : idx_(idx) {} + /*! + @brief Returns true if the idx of the argument entry is equal + to that of the object. + */ + bool operator()(const Entry& entry) const + { return idx_ == entry.idx(); } + + private: + int idx_; + + }; // class FindEntryByIdx + + //! Unary predicate that matches an Entry with a given tag + class FindEntryByTag { + public: + //! Constructor, initializes the object with the tag to look for + FindEntryByTag(uint16_t tag) : tag_(tag) {} + /*! + @brief Returns true if the tag of the argument entry is equal + to that of the object. + */ + bool operator()(const Entry& entry) const + { return tag_ == entry.tag(); } + + private: + uint16_t tag_; + + }; // class FindEntryByTag + + /*! + @brief Models an IFD (%Image File Directory) + + This class models an IFD as described in the TIFF 6.0 specification. + + An instance of class %Ifd can operate in two modes, one that allocates and + deallocates the memory required to store data, and one that doesn't + perform such memory management. + <BR>An external data buffer (not managed by %Ifd) is needed for an instance + of %Ifd which operates in no memory management mode. The %Ifd will + maintain only pointers into this buffer. + <BR> The mode without memory management is used to make "non-intrusive + write support" possible. This allows writing to Exif data of an image + without changing the data layout of the Exif data, to maximize chances + that tag data, which the Exif reader may not understand (e.g., the + Makernote) remains valid. A "non-intrusive write operation" is the + modification of tag data without increasing the data size. + + @note Use the mode with memory management (the default) if you are unsure + or if these memory management considerations are of no concern to you. + + @note The two different modes imply completely different copy and + assignment behaviours, with the first resulting in entirely separate + classes and the second mode resulting in multiple classes using one + and the same data buffer. + */ + class Ifd { + //! @name Not implemented + //@{ + //! Assignment not allowed (memory management mode alloc_ is const) + Ifd& operator=(const Ifd& rhs); + //@} + + public: + //! %Entries const iterator type + typedef Entries::const_iterator const_iterator; + //! %Entries iterator type + typedef Entries::iterator iterator; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to set the IFD identifier. Memory management + is enabled, offset is set to 0. Serves as default constructor. + */ + explicit Ifd(IfdId ifdId =ifdIdNotSet); + /*! + @brief Constructor. Allows to set the IFD identifier and the offset of + the IFD from the start of TIFF header. Memory management is + enabled. + */ + Ifd(IfdId ifdId, long offset); + /*! + @brief Constructor. Allows to set the IFD identifier, offset of the + IFD from the start of TIFF header, choose whether or not + memory management is required for the Entries, and decide + whether this IFD has a next pointer. + */ + Ifd(IfdId ifdId, long offset, bool alloc, bool hasNext =true); + //! Copy constructor + Ifd(const Ifd& rhs); + //! Destructor + ~Ifd(); + //@} + + //! @name Manipulators + //@{ + /*! + @brief Read a complete IFD and its data from a data buffer + + @param buf Pointer to the data to decode. The buffer must start with the + IFD data (unlike the readSubIfd() method). + @param len Number of bytes in the data buffer + @param byteOrder Applicable byte order (little or big endian). + @param offset (Optional) offset of the IFD from the start of the TIFF + header, if known. If not given, the offset will be guessed + using the assumption that the smallest offset of all IFD + directory entries points to a data buffer immediately follwing + the IFD. + + @return 0 if successful;<BR> + 6 if the data buffer is too small, e.g., if an offset points + beyond the provided buffer. The IFD is cleared in this + case. + */ + int read(const byte* buf, long len, ByteOrder byteOrder, long offset =0); + /*! + @brief Copy the IFD to a data array, update the offsets of the IFD and + all its entries, return the number of bytes written. + + First the number of IFD entries is written (2 bytes), followed + by all directory entries: tag (2), type (2), number of data + components (4) and offset to the data or the data, if it + occupies not more than four bytes (4). The directory entries + are followed by the offset of the next IFD (4). All these + fields are encoded according to the byte order argument. Data + that doesn't fit into the offset fields follows immediately + after the IFD entries. The offsets in the IFD are set to + correctly point to the data fields, using the offset parameter + or the offset of the IFD. + + @param buf Pointer to the data buffer. The user must ensure that the + buffer has enough memory. Otherwise the call results in + undefined behaviour. + @param byteOrder Applicable byte order (little or big endian). + @param offset Target offset from the start of the TIFF header of the + data array. The IFD offsets will be adjusted as necessary. If + not given, then it is assumed that the IFD will remain at its + original position, i.e., the offset of the IFD will be used. + @return Returns the number of characters written. + */ + long copy(byte* buf, ByteOrder byteOrder, long offset =0); + /*! + @brief Reset the IFD. Delete all IFD entries from the class and put + the object in a state where it can accept completely new + entries. + */ + void clear(); + /*! + @brief Set the offset of the next IFD. Byte order is needed to update + the underlying data buffer in non-alloc mode. This method only + has an effect if the IFD was instantiated with hasNext = true. + */ + void setNext(uint32_t next, ByteOrder byteOrder); + /*! + @brief Add the entry to the IFD. No duplicate-check is performed, + i.e., it is possible to add multiple entries with the same tag. + The memory allocation mode of the entry to be added must match + that of the IFD and the IFD ids of the IFD and entry must + match. + */ + void add(const Entry& entry); + /*! + @brief Delete the directory entry with the given tag. Return the index + of the deleted entry or 0 if no entry with tag was found. + */ + int erase(uint16_t tag); + /*! + @brief Delete the directory entry at iterator position pos, return the + position of the next entry. Note that iterators into the + directory, including pos, are potentially invalidated by this + call. + */ + iterator erase(iterator pos); + //! Sort the IFD entries by tag + void sortByTag(); + //! The first entry + iterator begin() { return entries_.begin(); } + //! End of the entries + iterator end() { return entries_.end(); } + //! Find an IFD entry by idx, return an iterator into the entries list + iterator findIdx(int idx); + //! Find an IFD entry by tag, return an iterator into the entries list + iterator findTag(uint16_t tag); + /*! + @brief Update the base pointer of the Ifd and all entries to \em pNewBase. + + Allows to re-locate the underlying data buffer to a new location + \em pNewBase. This method only has an effect in non-alloc mode. + + @param pNewBase Pointer to the new data buffer + + @return Old base pointer or 0 if called in alloc mode + */ + byte* updateBase(byte* pNewBase); + //@} + + //! @name Accessors + //@{ + /*! + @brief Read a sub-IFD from the location pointed to by the directory entry + with the given tag. + + @param dest References the destination IFD. + @param buf The data buffer to read from. The buffer must contain all Exif + data starting from the TIFF header (unlike the read() method). + @param len Number of bytes in the data buffer + @param byteOrder Applicable byte order (little or big endian). + @param tag Tag to look for. + + @return 0 if successful;<BR> + 6 if reading the sub-IFD failed (see read() above) or + the location pointed to by the directory entry with the + given tag is outside of the data buffer. + + @note It is not considered an error if the tag cannot be found in the + IFD. 0 is returned and no action is taken in this case. + */ + int readSubIfd( + Ifd& dest, const byte* buf, long len, ByteOrder byteOrder, uint16_t tag + ) const; + //! Get the memory allocation mode, see the Ifd class description for details + bool alloc() const { return alloc_; } + //! The first entry + const_iterator begin() const { return entries_.begin(); } + //! End of the entries + const_iterator end() const { return entries_.end(); } + //! Find an IFD entry by idx, return a const iterator into the entries list + const_iterator findIdx(int idx) const; + //! Find an IFD entry by tag, return a const iterator into the entries list + const_iterator findTag(uint16_t tag) const; + //! Get the IfdId of the IFD + IfdId ifdId() const { return ifdId_; } + //! Get the offset of the IFD from the start of the TIFF header + long offset() const { return offset_; } + /*! + @brief Get the offset of the first data entry outside of the IFD from + the start of the TIFF header, return 0 if there is none. The + data offset is determined when the IFD is read. + */ + long dataOffset() const { return dataOffset_; } + //! Get the offset to the next IFD from the start of the TIFF header + uint32_t next() const { return next_; } + //! Get the number of directory entries in the IFD + long count() const { return static_cast<long>(entries_.size()); } + //! Get the size of this IFD in bytes (IFD only, without data) + long size() const; + /*! + @brief Return the total size of the data of this IFD in bytes; sums + the size of all directory entries where size is greater than + four plus the size of all data areas, i.e., all data that + requires memory outside the IFD directory entries is counted. + */ + long dataSize() const; + /*! + @brief Print the IFD in human readable format to the given stream; + begin each line with prefix. + */ + void print(std::ostream& os, const std::string& prefix ="") const; + //@} + + private: + //! Helper structure to build IFD entries + struct PreEntry { + uint16_t tag_; + uint16_t type_; + uint32_t count_; + long size_; + long offsetLoc_; + long offset_; + }; + + //! cmpPreEntriesByOffset needs to know about PreEntry, that's all. + friend bool cmpPreEntriesByOffset(const PreEntry&, const PreEntry&); + + //! Container for 'pre-entries' + typedef std::vector<PreEntry> PreEntries; + + // DATA + /*! + True: requires memory allocation and deallocation, + False: no memory management needed. + */ + const bool alloc_; + //! IFD entries + Entries entries_; + //! IFD Id + IfdId ifdId_; + //! Pointer to IFD from the start of the TIFF header + byte* pBase_; + //! Offset of the IFD from the start of the TIFF header + long offset_; + //! Offset of the first data entry outside of the IFD directory + long dataOffset_; + //! Indicates whether the IFD has a next pointer + bool hasNext_; + //! Pointer to the offset of next IFD from the start of the TIFF header + byte* pNext_; + //! The offset of the next IFD as data value (always in sync with *pNext_) + uint32_t next_; + + }; // class Ifd + +// ***************************************************************************** +// free functions + + /*! + @brief Compare two IFD entries by tag. Return true if the tag of entry + lhs is less than that of rhs. + */ + bool cmpEntriesByTag(const Entry& lhs, const Entry& rhs); + + /*! + @brief Compare two 'pre-IFD entries' by offset, taking care of special + cases where one or both of the entries don't have an offset. + Return true if the offset of entry lhs is less than that of rhs, + else false. By definition, entries without an offset are greater + than those with an offset. + */ + bool cmpPreEntriesByOffset(const Ifd::PreEntry& lhs, const Ifd::PreEntry& rhs); + +} // namespace Exiv2 + +#endif // #ifndef IFD_HPP_ diff --git a/src/plugins/exiv2/image.cpp b/src/plugins/exiv2/image.cpp @@ -0,0 +1,223 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: image.cpp + Version: $Rev: 563 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + Brad Schick (brad) <brad@robotbattle.com> + History: 26-Jan-04, ahu: created + 11-Feb-04, ahu: isolated as a component + 19-Jul-04, brad: revamped to be more flexible and support Iptc + 15-Jan-05, brad: inside-out design changes + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: image.cpp 563 2005-04-21 07:21:53Z ahuggel $"); + +// ***************************************************************************** +// included header files +#ifdef _MSC_VER +# include "exv_msvc.h" +#else +# include "exv_conf.h" +#endif + +#include "image.hpp" +#include "error.hpp" +#include "futils.hpp" + +// Ensure registration with factory +#include "jpgimage.hpp" + +// + standard includes +#include <cerrno> +#include <cstdio> +#include <cstring> +#include <cstdio> // for rename, remove +#include <cassert> +#include <sys/types.h> +#include <sys/stat.h> +#ifdef _MSC_VER +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif +#ifdef EXV_HAVE_UNISTD_H +# include <unistd.h> // stat +#endif + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + ImageFactory::Registry* ImageFactory::registry_ = 0; + + void ImageFactory::init() + { + if (0 == registry_) { + registry_ = new Registry; + } + } + + void ImageFactory::registerImage(Image::Type type, + NewInstanceFct newInst, IsThisTypeFct isType) + { + init(); + assert (newInst && isType); + (*registry_)[type] = ImageFcts(newInst, isType); + } + + Image::Type ImageFactory::getType(const std::string& path) + { + FileIo fileIo(path); + return getType(fileIo); + } + + Image::Type ImageFactory::getType(const byte* data, long size) + { + MemIo memIo(data, size); + return getType(memIo); + } + + Image::Type ImageFactory::getType(BasicIo& io) + { + if (io.open() != 0) return Image::none; + IoCloser closer(io); + Image::Type type = Image::none; + Registry::const_iterator b = registry_->begin(); + Registry::const_iterator e = registry_->end(); + for (Registry::const_iterator i = b; i != e; ++i) + { + if (i->second.isThisType(io, false)) { + type = i->first; + break; + } + } + return type; + } // ImageFactory::getType + + Image::AutoPtr ImageFactory::open(const std::string& path) + { + BasicIo::AutoPtr io(new FileIo(path)); + Image::AutoPtr image = open(io); // may throw + if (image.get() == 0) throw Error(11, path); + return image; + } + + Image::AutoPtr ImageFactory::open(const byte* data, long size) + { + BasicIo::AutoPtr io(new MemIo(data, size)); + Image::AutoPtr image = open(io); // may throw + if (image.get() == 0) throw Error(12); + return image; + } + + Image::AutoPtr ImageFactory::open(BasicIo::AutoPtr io) + { + if (io->open() != 0) { + throw Error(9, io->path(), strError()); + } + Image::AutoPtr image; + Registry::const_iterator b = registry_->begin(); + Registry::const_iterator e = registry_->end(); + for (Registry::const_iterator i = b; i != e; ++i) { + if (i->second.isThisType(*io, false)) { + image = i->second.newInstance(io, false); + break; + } + } + return image; + } // ImageFactory::open + + Image::AutoPtr ImageFactory::create(Image::Type type, + const std::string& path) + { + std::auto_ptr<FileIo> fileIo(new FileIo(path)); + // Create or overwrite the file, then close it + if (fileIo->open("w+b") != 0) { + throw Error(10, path, "w+b", strError()); + } + fileIo->close(); + BasicIo::AutoPtr io(fileIo); + Image::AutoPtr image = create(type, io); + if (image.get() == 0) throw Error(13, type); + return image; + } + + Image::AutoPtr ImageFactory::create(Image::Type type) + { + BasicIo::AutoPtr io(new MemIo); + Image::AutoPtr image = create(type, io); + if (image.get() == 0) throw Error(13, type); + return image; + } + + Image::AutoPtr ImageFactory::create(Image::Type type, + BasicIo::AutoPtr io) + { + // BasicIo instance does not need to be open + Registry::const_iterator i = registry_->find(type); + if (i != registry_->end()) { + return i->second.newInstance(io, true); + } + return Image::AutoPtr(); + } // ImageFactory::create + + TiffHeader::TiffHeader(ByteOrder byteOrder) + : byteOrder_(byteOrder), tag_(0x002a), offset_(0x00000008) + { + } + + int TiffHeader::read(const byte* buf) + { + if (buf[0] == 0x49 && buf[1] == 0x49) { + byteOrder_ = littleEndian; + } + else if (buf[0] == 0x4d && buf[1] == 0x4d) { + byteOrder_ = bigEndian; + } + else { + return 1; + } + tag_ = getUShort(buf+2, byteOrder_); + offset_ = getULong(buf+4, byteOrder_); + return 0; + } + + long TiffHeader::copy(byte* buf) const + { + switch (byteOrder_) { + case littleEndian: + buf[0] = 0x49; + buf[1] = 0x49; + break; + case bigEndian: + buf[0] = 0x4d; + buf[1] = 0x4d; + break; + case invalidByteOrder: + // do nothing + break; + } + us2Data(buf+2, 0x002a, byteOrder_); + ul2Data(buf+4, 0x00000008, byteOrder_); + return size(); + } // TiffHeader::copy + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/image.hpp b/src/plugins/exiv2/image.hpp @@ -0,0 +1,461 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file image.hpp + @brief Class JpegImage to access JPEG images + @version $Rev: 563 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @author Brad Schick (brad) + <a href="mailto:brad@robotbattle.com">brad@robotbattle.com</a> + @date 09-Jan-04, ahu: created<BR> + 11-Feb-04, ahu: isolated as a component<BR> + 19-Jul-04, brad: revamped to be more flexible and support Iptc + 15-Jan-05, brad: inside-out design changes + */ +#ifndef IMAGE_HPP_ +#define IMAGE_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "basicio.hpp" + +// + standard includes +#include <string> +#include <map> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class ExifData; + class IptcData; + +// ***************************************************************************** +// class definitions + /*! + @brief Abstract base class defining the interface for an image. This is + the top-level interface to the Exiv2 library. + + Most client apps will obtain an Image instance by calling a static + ImageFactory method. The Image class can then be used to to + read, write, and save metadata. + */ + class Image { + public: + //! Supported image formats + enum Type { none, jpeg, exv }; + + //! Image auto_ptr type + typedef std::auto_ptr<Image> AutoPtr; + + //! @name Creators + //@{ + //! Virtual Destructor + virtual ~Image() {} + //@} + + //! @name Manipulators + //@{ + /*! + @brief Read metadata from assigned image. Before this method + is called, the various metadata types (Iptc, Exif) will be empty. + @throw Error In case of failure. + */ + virtual void readMetadata() =0; + /*! + @brief Write metadata back to the image. + + All existing metadata sections in the image are either created, + replaced, or erased. If values for a given metadata type have been + assigned, a section for that metadata type will either be created or + replaced. If no values have been assigned to a given metadata type, + any exists section for that metadata type will be removed from the + image. + + @throw Error if the operation fails + */ + virtual void writeMetadata() =0; + /*! + @brief Assign new exif data. The new exif data is not written + to the image until the writeMetadata() method is called. + @param exifData An ExifData instance holding exif data to be copied + */ + virtual void setExifData(const ExifData& exifData) =0; + /*! + @brief Erase any buffered Exif data. Exif data is not removed from + the actual image until the writeMetadata() method is called. + */ + virtual void clearExifData() =0; + /*! + @brief Assign new iptc data. The new iptc data is not written + to the image until the writeMetadata() method is called. + @param iptcData An IptcData instance holding iptc data to be copied + */ + virtual void setIptcData(const IptcData& iptcData) =0; + /*! + @brief Erase any buffered Iptc data. Iptc data is not removed from + the actual image until the writeMetadata() method is called. + */ + virtual void clearIptcData() =0; + /*! + @brief Set the image comment. The new comment is not written + to the image until the writeMetadata() method is called. + @param comment String containing comment. + */ + virtual void setComment(const std::string& comment) =0; + /*! + @brief Erase any buffered comment. Comment is not removed + from the actual image until the writeMetadata() method is called. + */ + virtual void clearComment() =0; + /*! + @brief Copy all existing metadata from source Image. The data is + copied into internal buffers and is not written to the image + until the writeMetadata() method is called. + @param image Metadata source. All metadata types are copied. + */ + virtual void setMetadata(const Image& image) =0; + /*! + @brief Erase all buffered metadata. Metadata is not removed + from the actual image until the writeMetadata() method is called. + */ + virtual void clearMetadata() =0; + //@} + + //! @name Accessors + //@{ + /*! + @brief Check if the Image instance is valid. Use after object + construction. + @return true if the Image is in a valid state. + */ + virtual bool good() const =0; + /*! + @brief Returns an ExifData instance containing currently buffered + exif data. + + The exif data may have been read from the image by + a previous call to readMetadata() or added directly. The exif + data in the returned instance will be written to the image when + writeMetadata() is called. + + @return read only ExifData instance containing exif values + */ + virtual const ExifData& exifData() const =0; + /*! + @brief Returns an ExifData instance containing currently buffered + exif data. + + The contained exif data may have been read from the image by + a previous call to readMetadata() or added directly. The exif + data in the returned instance will be written to the image when + writeMetadata() is called. + + @return modifiable ExifData instance containing exif values + */ + virtual ExifData& exifData() =0; + /*! + @brief Returns an IptcData instance containing currently buffered + iptc data. + + The contained iptc data may have been read from the image by + a previous call to readMetadata() or added directly. The iptc + data in the returned instance will be written to the image when + writeMetadata() is called. + + @return modifiable IptcData instance containing iptc values + */ + virtual const IptcData& iptcData() const =0; + /*! + @brief Returns an ExifData instance containing currently buffered + exif data. + + The contained iptc data may have been read from the image by + a previous call to readMetadata() or added directly. The iptc + data in the returned instance will be written to the image when + writeMetadata() is called. + + @return modifiable IptcData instance containing iptc values + */ + virtual IptcData& iptcData() =0; + /*! + @brief Return a copy of the image comment. May be an empty string. + */ + virtual std::string comment() const =0; + /*! + @brief Return a reference to the BasicIo instance being used for Io. + + This refence is particularly useful to reading the results of + operations on a MemIo instance. For example after metadata has + been modified and the writeMetadata() method has been called, + this method can be used to get access to the modified image. + + @return BasicIo instance that can be used to read or write image + data directly. + @note If the returned BasicIo is used to write to the image, the + Image class will not see those changes until the readMetadata() + method is called. + */ + virtual BasicIo& io() const = 0; + //@} + + protected: + //! @name Creators + //@{ + //! Default Constructor + Image() {} + //@} + + private: + // NOT Implemented + //! Copy constructor + Image(const Image& rhs); + //! Assignment operator + Image& operator=(const Image& rhs); + + }; // class Image + + //! Type for function pointer that creates new Image instances + typedef Image::AutoPtr (*NewInstanceFct)(BasicIo::AutoPtr io, bool create); + //! Type for function pointer that checks image types + typedef bool (*IsThisTypeFct)(BasicIo& iIo, bool advance); + + /*! + @brief Returns an Image instance of the specified type. + + The factory is implemented as a singleton, which can be accessed + through static member functions. + */ + class ImageFactory { + public: + //! @name Manipulators + //@{ + /*! + @brief Register image type together with its function pointers. + + The image factory creates new images by calling their associated + function pointer. Additional images can be added by registering + new type and function pointers. If called for a type that already + exists in the list, the corresponding functions are replaced. + + @param type Image type. + @param newInst Function pointer for creating image instances. + @param isType Function pointer to test for matching image types. + */ + static void registerImage(Image::Type type, + NewInstanceFct newInst, + IsThisTypeFct isType); + //@} + + //! @name Accessors + //@{ + /*! + @brief Create an Image subclass of the appropriate type by reading + the specified file. %Image type is derived from the file + contents. + @param path %Image file. The contents of the file are tested to + determine the image type. File extension is ignored. + @return An auto-pointer that owns an Image instance whose type + matches that of the file. + @throw Error If opening the file fails or it contains data of an + unknown image type. + */ + static Image::AutoPtr open(const std::string& path); + /*! + @brief Create an Image subclass of the appropriate type by reading + the provided memory. %Image type is derived from the memory + contents. + @param data Pointer to a data buffer containing an image. The contents + of the memory are tested to determine the image type. + @param size Number of bytes pointed to by \em data. + @return An auto-pointer that owns an Image instance whose type + matches that of the data buffer. + @throw Error If the memory contains data of an unknown image type. + */ + static Image::AutoPtr open(const byte* data, long size); + /*! + @brief Create an Image subclass of the appropriate type by reading + the provided BasicIo instance. %Image type is derived from the + data provided by \em io. The passed in \em io instance is + (re)opened by this method. + @param io An auto-pointer that owns a BasicIo instance that provides + image data. The contents of the image data are tested to determine + the type. + @note This method takes ownership of the passed + in BasicIo instance through the auto-pointer. Callers should not + continue to use the BasicIo instance after it is passed to this method. + Use the Image::io() method to get a temporary reference. + @return An auto-pointer that owns an Image instance whose type + matches that of the \em io data. If no image type could be + determined, the pointer is 0. + @throw Error If opening the BasicIo fails + */ + static Image::AutoPtr open(BasicIo::AutoPtr io); + /*! + @brief Create an Image subclass of the requested type by creating a + new image file. If the file already exists, it will be overwritten. + @param type Type of the image to be created. + @param path %Image file to create. File extension is ignored. + @return An auto-pointer that owns an Image instance of the requested + type. + @throw Error If the image type is not supported. + */ + static Image::AutoPtr create(Image::Type type, const std::string& path); + /*! + @brief Create an Image subclass of the requested type by creating a + new image in memory. + @param type Type of the image to be created. + @return An auto-pointer that owns an Image instance of the requested + type. + @throw Error If the image type is not supported + */ + static Image::AutoPtr create(Image::Type type); + /*! + @brief Create an Image subclass of the requested type by writing a + new image to a BasicIo instance. If the BasicIo instance already + contains data, it will be overwritten. + @param type Type of the image to be created. + @param io An auto-pointer that owns a BasicIo instance that will + be written to when creating a new image. + @note This method takes ownership of the passed in BasicIo instance + through the auto-pointer. Callers should not continue to use the + BasicIo instance after it is passed to this method. Use the + Image::io() method to get a temporary reference. + @return An auto-pointer that owns an Image instance of the requested + type. If the image type is not supported, the pointer is 0. + */ + static Image::AutoPtr create(Image::Type type, BasicIo::AutoPtr io); + /*! + @brief Returns the image type of the provided file. + @param path %Image file. The contents of the file are tested to + determine the image type. File extension is ignored. + @return %Image type or Image::none if the type is not recognized. + */ + static Image::Type getType(const std::string& path); + /*! + @brief Returns the image type of the provided data buffer. + @param data Pointer to a data buffer containing an image. The contents + of the memory are tested to determine the image type. + @param size Number of bytes pointed to by \em data. + @return %Image type or Image::none if the type is not recognized. + */ + static Image::Type getType(const byte* data, long size); + /*! + @brief Returns the image type of data provided by a BasicIo instance. + The passed in \em io instance is (re)opened by this method. + @param io A BasicIo instance that provides image data. The contents + of the image data are tested to determine the type. + @return %Image type or Image::none if the type is not recognized. + */ + static Image::Type getType(BasicIo& io); + //@} + + + private: + //! @name Creators + //@{ + //! Prevent construction other than through instance(). + ImageFactory(); + //! Prevent copy construction: not implemented. + ImageFactory(const ImageFactory& rhs); + //! Creates the private static instance + static void init(); + //@} + + //! Struct for storing image function pointers. + struct ImageFcts + { + NewInstanceFct newInstance; + IsThisTypeFct isThisType; + ImageFcts(NewInstanceFct newInst, IsThisTypeFct isType) + : newInstance(newInst), isThisType(isType) {} + ImageFcts() : newInstance(0), isThisType(0) {} + }; + + // DATA + //! Type used to store Image creation functions + typedef std::map<Image::Type, ImageFcts> Registry; + //! List of image types and corresponding creation functions. + static Registry* registry_; + }; // class ImageFactory + + + //! Helper class modelling the TIFF header structure. + class TiffHeader { + public: + //! @name Creators + //@{ + /*! + @brief Default constructor. Optionally sets the byte order + (default: little endian). + */ + explicit TiffHeader(ByteOrder byteOrder =littleEndian); + //@} + + //! @name Manipulators + //@{ + //! Read the TIFF header from a data buffer. Returns 0 if successful. + int read(const byte* buf); + //@} + + //! @name Accessors + //@{ + /*! + @brief Write a standard TIFF header into buf as a data string, return + number of bytes copied. + + Only the byte order of the TIFF header varies, the values written for + offset and tag are constant, i.e., independent of the values possibly + read before a call to this function. The value 0x00000008 is written + for the offset, tag is set to 0x002a. + + @param buf The data buffer to write to. + @return The number of bytes written. + */ + long copy(byte* buf) const; + //! Return the size of the TIFF header in bytes. + long size() const { return 8; } + //! Return the byte order (little or big endian). + ByteOrder byteOrder() const { return byteOrder_; } + //! Return the tag value. + uint16_t tag() const { return tag_; } + /*! + @brief Return the offset to IFD0 from the start of the TIFF header. + The offset is 0x00000008 if IFD0 begins immediately after the + TIFF header. + */ + uint32_t offset() const { return offset_; } + //@} + + private: + ByteOrder byteOrder_; + uint16_t tag_; + uint32_t offset_; + + }; // class TiffHeader + +} // namespace Exiv2 + +#endif // #ifndef IMAGE_HPP_ diff --git a/src/plugins/exiv2/iptc.cpp b/src/plugins/exiv2/iptc.cpp @@ -0,0 +1,298 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: iptc.cpp + Version: $Rev: 560 $ + Author(s): Brad Schick (brad) <brad@robotbattle.com> + History: 31-July-04, brad: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: iptc.cpp 560 2005-04-17 11:51:32Z ahuggel $"); + +// Define DEBUG_MAKERNOTE to output debug information to std::cerr +#undef DEBUG_MAKERNOTE + +// ***************************************************************************** +// included header files +#include "iptc.hpp" +#include "types.hpp" +#include "error.hpp" +#include "value.hpp" +#include "datasets.hpp" +#include "jpgimage.hpp" + +// + standard includes +#include <iostream> +#include <algorithm> + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + Iptcdatum::Iptcdatum(const IptcKey& key, + const Value* pValue) + : key_(key.clone()) + { + if (pValue) value_ = pValue->clone(); + } + + Iptcdatum::Iptcdatum(const Iptcdatum& rhs) + : Metadatum(rhs) + { + if (rhs.key_.get() != 0) key_ = rhs.key_->clone(); // deep copy + if (rhs.value_.get() != 0) value_ = rhs.value_->clone(); // deep copy + } + + Iptcdatum::~Iptcdatum() + { + } + + Iptcdatum& Iptcdatum::operator=(const Iptcdatum& rhs) + { + if (this == &rhs) return *this; + Metadatum::operator=(rhs); + + 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; + } // Iptcdatum::operator= + + Iptcdatum& Iptcdatum::operator=(const uint16_t& value) + { + UShortValue::AutoPtr v = UShortValue::AutoPtr(new UShortValue); + v->value_.push_back(value); + value_ = v; + return *this; + } + + Iptcdatum& Iptcdatum::operator=(const std::string& value) + { + setValue(value); + return *this; + } + + Iptcdatum& Iptcdatum::operator=(const Value& value) + { + setValue(&value); + return *this; + } + + void Iptcdatum::setValue(const Value* pValue) + { + value_.reset(); + if (pValue) value_ = pValue->clone(); + } + + void Iptcdatum::setValue(const std::string& value) + { + if (value_.get() == 0) { + TypeId type = IptcDataSets::dataSetType(tag(), record()); + value_ = Value::create(type); + } + value_->read(value); + } + + const byte IptcData::marker_ = 0x1C; // Dataset marker + + Iptcdatum& IptcData::operator[](const std::string& key) + { + IptcKey iptcKey(key); + iterator pos = findKey(iptcKey); + if (pos == end()) { + add(Iptcdatum(iptcKey)); + pos = findKey(iptcKey); + } + return *pos; + } + + int IptcData::load(const byte* buf, long len) + { + const byte* pRead = buf; + iptcMetadata_.clear(); + + int rc = 0; + uint16_t record = 0; + uint16_t dataSet = 0; + uint32_t sizeData = 0; + byte extTest = 0; + + while (pRead + 3 < buf + len) { + if (*pRead++ != marker_) return 5; + record = *pRead++; + dataSet = *pRead++; + + extTest = *pRead; + if (extTest & 0x80) { + // extended dataset + uint16_t sizeOfSize = (getUShort(pRead, bigEndian) & 0x7FFF); + if (sizeOfSize > 4) return 5; + pRead += 2; + sizeData = 0; + for (; sizeOfSize > 0; --sizeOfSize) { + sizeData |= *pRead++ << (8 *(sizeOfSize-1)); + } + } + else { + // standard dataset + sizeData = getUShort(pRead, bigEndian); + pRead += 2; + } + rc = readData(dataSet, record, pRead, sizeData); + if( rc ) return rc; + pRead += sizeData; + } + + return rc; + } // IptcData::read + + int IptcData::readData(uint16_t dataSet, uint16_t record, + const byte* data, uint32_t sizeData) + { + Value::AutoPtr value; + TypeId type = IptcDataSets::dataSetType(dataSet, record); + value = Value::create(type); + value->read(data, sizeData, bigEndian); + IptcKey key(dataSet, record); + add(key, value.get()); + return 0; + } + + DataBuf IptcData::copy() + { + DataBuf buf(size()); + byte *pWrite = buf.pData_; + + const_iterator iter = iptcMetadata_.begin(); + const_iterator end = iptcMetadata_.end(); + for ( ; iter != end; ++iter) { + // marker, record Id, dataset num + *pWrite++ = marker_; + *pWrite++ = static_cast<byte>(iter->record()); + *pWrite++ = static_cast<byte>(iter->tag()); + + // extended or standard dataset? + long dataSize = iter->size(); + if (dataSize > 32767) { + // always use 4 bytes for extended length + uint16_t sizeOfSize = 4 | 0x8000; + us2Data(pWrite, sizeOfSize, bigEndian); + pWrite += 2; + ul2Data(pWrite, dataSize, bigEndian); + pWrite += 4; + } + else { + us2Data(pWrite, static_cast<uint16_t>(dataSize), bigEndian); + pWrite += 2; + } + + pWrite += iter->value().copy(pWrite, bigEndian); + } + + return buf; + } // IptcData::updateBuffer + + long IptcData::size() const + { + long newSize = 0; + const_iterator iter = iptcMetadata_.begin(); + const_iterator end = iptcMetadata_.end(); + for ( ; iter != end; ++iter) { + // marker, record Id, dataset num, first 2 bytes of size + newSize += 5; + long dataSize = iter->size(); + newSize += dataSize; + if (dataSize > 32767) { + // extended dataset (we always use 4 bytes) + newSize += 4; + } + } + return newSize; + } // IptcData::size + + int IptcData::add(const IptcKey& key, Value* value) + { + return add(Iptcdatum(key, value)); + } + + int IptcData::add(const Iptcdatum& iptcDatum) + { + if (!IptcDataSets::dataSetRepeatable( + iptcDatum.tag(), iptcDatum.record()) && + findId(iptcDatum.tag(), iptcDatum.record()) != end()) { + return 6; + } + // allow duplicates + iptcMetadata_.push_back(iptcDatum); + return 0; + } + + IptcData::const_iterator IptcData::findKey(const IptcKey& key) const + { + return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), + FindMetadatumById(key.tag(), key.record())); + } + + IptcData::iterator IptcData::findKey(const IptcKey& key) + { + return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), + FindMetadatumById(key.tag(), key.record())); + } + + IptcData::const_iterator IptcData::findId(uint16_t dataset, uint16_t record) const + { + return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), + FindMetadatumById(dataset, record)); + } + + IptcData::iterator IptcData::findId(uint16_t dataset, uint16_t record) + { + return std::find_if(iptcMetadata_.begin(), iptcMetadata_.end(), + FindMetadatumById(dataset, record)); + } + + void IptcData::sortByKey() + { + std::sort(iptcMetadata_.begin(), iptcMetadata_.end(), cmpMetadataByKey); + } + + void IptcData::sortByTag() + { + std::sort(iptcMetadata_.begin(), iptcMetadata_.end(), cmpMetadataByTag); + } + + IptcData::iterator IptcData::erase(IptcData::iterator pos) + { + return iptcMetadata_.erase(pos); + } + + // ************************************************************************* + // free functions + std::ostream& operator<<(std::ostream& os, const Iptcdatum& md) + { + return os << md.value(); + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/iptc.hpp b/src/plugins/exiv2/iptc.hpp @@ -0,0 +1,416 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file iptc.hpp + @brief Encoding and decoding of Iptc data + @version $Rev: 560 $ + @author Brad Schick (brad) + <a href="mailto:brad@robotbattle.com">brad@robotbattle.com</a> + @date 31-Jul-04, brad: created + */ +#ifndef IPTC_HPP_ +#define IPTC_HPP_ + +// ***************************************************************************** +// included header files +#include "metadatum.hpp" +#include "types.hpp" +#include "error.hpp" +#include "value.hpp" +#include "datasets.hpp" + +// + standard includes +#include <string> +#include <vector> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class definitions + + /*! + @brief Information related to one Iptc dataset. An Iptc metadatum consists + of an IptcKey and a Value and provides methods to manipulate these. + */ + class Iptcdatum : public Metadatum { + public: + //! @name Creators + //@{ + /*! + @brief Constructor for new tags created by an application. The + %Iptcdatum is created from a key / value pair. %Iptcdatum + copies (clones) the value if one is provided. Alternatively, a + program can create an 'empty' %Iptcdatum with only a key and + set the value using setValue(). + + @param key The key of the %Iptcdatum. + @param pValue Pointer to a %Iptcdatum value. + @throw Error if the key cannot be parsed and converted + to a tag number and record id. + */ + explicit Iptcdatum(const IptcKey& key, + const Value* pValue =0); + //! Copy constructor + Iptcdatum(const Iptcdatum& rhs); + //! Destructor + virtual ~Iptcdatum(); + //@} + + //! @name Manipulators + //@{ + //! Assignment operator + Iptcdatum& operator=(const Iptcdatum& rhs); + /*! + @brief Assign \em value to the %Iptcdatum. The type of the new Value + is set to UShortValue. + */ + Iptcdatum& operator=(const uint16_t& value); + /*! + @brief Assign \em value to the %Iptcdatum. + Calls setValue(const std::string&). + */ + Iptcdatum& operator=(const std::string& value); + /*! + @brief Assign \em value to the %Iptcdatum. + 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 + Value::read(const std::string&). + If the %Iptcdatum does not have a Value yet, then a %Value of + the correct type for this %Iptcdatum is created. If that + fails (because of an unknown dataset), a StringValue is + created. + */ + void setValue(const std::string& value); + //@} + + //! @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 + multiple metadata with the same key. + */ + std::string key() const { return key_.get() == 0 ? "" : key_->key(); } + /*! + @brief Return the name of the record + @return record name + */ + std::string recordName() const + { return key_.get() == 0 ? "" : key_->recordName(); } + /*! + @brief Return the record id + @return record id + */ + uint16_t record() const + { return key_.get() == 0 ? 0 : key_->record(); } + /*! + @brief Return the name of the tag (aka dataset) + @return tag name + */ + std::string tagName() const + { return key_.get() == 0 ? "" : key_->tagName(); } + //! 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. + */ + 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 + { if (value_.get() != 0) return *value_; throw Error(8); } + //@} + + private: + // DATA + IptcKey::AutoPtr key_; //!< Key + Value::AutoPtr value_; //!< Value + + }; // class Iptcdatum + + /*! + @brief Output operator for Iptcdatum types, printing the interpreted + tag value. + */ + std::ostream& operator<<(std::ostream& os, const Iptcdatum& md); + + //! Container type to hold all metadata + typedef std::vector<Iptcdatum> IptcMetadata; + + //! Unary predicate that matches an Iptcdatum with given record and dataset + class FindMetadatumById { + public: + //! Constructor, initializes the object with the record and dataset id + FindMetadatumById(uint16_t dataset, uint16_t record) + : dataset_(dataset), record_(record) {} + /*! + @brief Returns true if the record and dataset id of the argument + Iptcdatum is equal to that of the object. + */ + bool operator()(const Iptcdatum& iptcdatum) const + { return dataset_ == iptcdatum.tag() && record_ == iptcdatum.record(); } + + private: + uint16_t dataset_; + uint16_t record_; + + }; // class FindMetadatumById + + /*! + @brief A container for Iptc data. This is a top-level class of + the %Exiv2 library. + + Provide high-level access to the Iptc data of an image: + - read Iptc information from JPEG files + - access metadata through keys and standard C++ iterators + - add, modify and delete metadata + - write Iptc data to JPEG files + - extract Iptc metadata to files, insert from these files + */ + class IptcData { + public: + //! IptcMetadata iterator type + typedef IptcMetadata::iterator iterator; + //! IptcMetadata const iterator type + typedef IptcMetadata::const_iterator const_iterator; + + // Use the compiler generated constructors and assignment operator + + //! @name Manipulators + //@{ + /*! + @brief Load the Iptc data from a byte buffer. The format must follow + the IPTC IIM4 standard. + @param buf Pointer to the data buffer to read from + @param len Number of bytes in the data buffer + @return 0 if successful;<BR> + 5 if Iptc data is invalid or corrupt;<BR> + */ + int load(const byte* buf, long len); + /*! + @brief Write the Iptc data to a data buffer and return the data buffer. + Caller owns this buffer. The copied data follows the IPTC IIM4 + standard. + @return Data buffer containing the Iptc data. + */ + DataBuf copy(); + /*! + @brief Returns a reference to the %Iptcdatum that is associated with a + particular \em key. If %IptcData does not already contain such + an %Iptcdatum, operator[] adds object \em Iptcdatum(key). + + @note Since operator[] might insert a new element, it can't be a const + member function. + */ + Iptcdatum& operator[](const std::string& key); + /*! + @brief Add an %Iptcdatum from the supplied key and value pair. This + method copies (clones) the value. A check for non-repeatable + datasets is performed. + @return 0 if successful;<BR> + 6 if the dataset already exists and is not repeatable + */ + int add(const IptcKey& key, Value* value); + /*! + @brief Add a copy of the Iptcdatum to the Iptc metadata. A check + for non-repeatable datasets is performed. + @return 0 if successful;<BR> + 6 if the dataset already exists and is not repeatable;<BR> + */ + int add(const Iptcdatum& iptcdatum); + /*! + @brief Delete the Iptcdatum at iterator position pos, return the + position of the next Iptcdatum. Note that iterators into + the metadata, including pos, are potentially invalidated + by this call. + */ + iterator erase(iterator pos); + /*! + @brief Delete all Iptcdatum instances resulting in an empty container. + */ + void clear() { iptcMetadata_.clear(); } + //! Sort metadata by key + void sortByKey(); + //! Sort metadata by tag (aka dataset) + void sortByTag(); + //! Begin of the metadata + iterator begin() { return iptcMetadata_.begin(); } + //! End of the metadata + iterator end() { return iptcMetadata_.end(); } + /*! + @brief Find a Iptcdatum with the given key, return an iterator to it. + If multiple entries with the same key exist, it is undefined + which of the matching metadata is found. + */ + iterator findKey(const IptcKey& key); + /*! + @brief Find a Iptcdatum with the given record and dataset it, + return a const iterator to it. If multiple entries with the + same Ids exists, it is undefined which of the matching + metadata is found. + */ + iterator findId(uint16_t dataset, + uint16_t record = IptcDataSets::application2); + //@} + + //! @name Accessors + //@{ + //! Begin of the metadata + const_iterator begin() const { return iptcMetadata_.begin(); } + //! End of the metadata + const_iterator end() const { return iptcMetadata_.end(); } + /*! + @brief Find an Iptcdatum with the given key, return a const iterator + to it. If multiple metadata with the same key exist it is + undefined which of the matching metadata is found. + */ + const_iterator findKey(const IptcKey& key) const; + /*! + @brief Find a Iptcdatum with the given record and dataset number, + return a const iterator to it. If multiple metadata with the + same Ids exist it is undefined which of the matching + metadata is found. + */ + const_iterator findId(uint16_t dataset, + uint16_t record = IptcDataSets::application2) const; + //! Return true if there is no Iptc metadata + bool empty() const { return count() == 0; } + //! Get the number of metadata entries + long count() const { return static_cast<long>(iptcMetadata_.size()); } + /*! + @brief Return the exact size of all contained Iptc metadata + */ + long size() const; + //@} + + private: + /*! + @brief Read a single dataset payload and create a new metadata entry + @param dataSet DataSet number + @param record Record Id + @param data Pointer to the first byte of dataset payload + @param sizeData Length in bytes of dataset payload + @return 0 if successful. + */ + int readData(uint16_t dataSet, uint16_t record, + const byte* data, uint32_t sizeData); + + // Constant data + static const byte marker_; // Dataset marker + + // DATA + IptcMetadata iptcMetadata_; + }; // class IptcData + +} // namespace Exiv2 + +#endif // #ifndef IPTC_HPP_ diff --git a/src/plugins/exiv2/iptceasy.cpp b/src/plugins/exiv2/iptceasy.cpp @@ -0,0 +1,49 @@ +// ***************************************************************** -*- C++ -*- +// iptceasy.cpp, $Rev: 560 $ +// The quickest way to access, set or modify Iptc metadata. + +#include "iptc.hpp" +#include "image.hpp" +#include <iostream> +#include <iomanip> +#include <cassert> + +int main(int argc, char* const argv[]) +try { + if (argc != 2) { + std::cout << "Usage: " << argv[0] << " file\n"; + return 1; + } + std::string file(argv[1]); + + Exiv2::IptcData iptcData; + + iptcData["Iptc.Application2.Headline"] = "The headline I am"; + iptcData["Iptc.Application2.Keywords"] = "Yet another keyword"; + iptcData["Iptc.Application2.DateCreated"] = "2004-8-3"; + iptcData["Iptc.Application2.Urgency"] = uint16_t(1); + iptcData["Iptc.Envelope.ModelVersion"] = 42; + iptcData["Iptc.Envelope.TimeSent"] = "14:41:0-05:00"; + iptcData["Iptc.Application2.RasterizedCaption"] = "230 42 34 2 90 84 23 146"; + iptcData["Iptc.0x0009.0x0001"] = "Who am I?"; + + Exiv2::StringValue value; + value.read("very!"); + iptcData["Iptc.Application2.Urgency"] = value; + + std::cout << "Time sent: " << iptcData["Iptc.Envelope.TimeSent"] << "\n"; + + // Open image file + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(file); + assert (image.get() != 0); + + // Set Iptc data and write it to the file + image->setIptcData(iptcData); + image->writeMetadata(); + + return 0; +} +catch (Exiv2::AnyError& e) { + std::cout << "Caught Exiv2 exception '" << e << "'\n"; + return -1; +} diff --git a/src/plugins/exiv2/iptcprint.cpp b/src/plugins/exiv2/iptcprint.cpp @@ -0,0 +1,50 @@ +// ***************************************************************** -*- C++ -*- +// iptcprint.cpp, $Rev: 578 $ +// Sample program to print the Iptc metadata of an image + +#include "image.hpp" +#include "iptc.hpp" +#include <iostream> +#include <iomanip> +#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(); + + Exiv2::IptcData &iptcData = image->iptcData(); + if (iptcData.empty()) { + std::string error(argv[1]); + error += ": No Iptc data found in the file"; + throw Exiv2::Error(1, error); + } + + Exiv2::IptcData::iterator end = iptcData.end(); + for (Exiv2::IptcData::iterator md = iptcData.begin(); md != end; ++md) { + std::cout << std::setw(44) << std::setfill(' ') << std::left + << md->key() << " " + << "0x" << std::setw(4) << std::setfill('0') << std::right + << std::hex << md->tag() << " " + << std::setw(9) << std::setfill(' ') << std::left + << md->typeName() << " " + << std::dec << std::setw(3) + << std::setfill(' ') << std::right + << md->count() << " " + << std::dec << md->value() + << std::endl; + } + + return 0; +} +catch (Exiv2::AnyError& e) { + std::cout << "Caught Exiv2 exception '" << e << "'\n"; + return -1; +} diff --git a/src/plugins/exiv2/jpgimage.cpp b/src/plugins/exiv2/jpgimage.cpp @@ -0,0 +1,661 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: jpgimage.cpp + Version: $Rev: 563 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + Brad Schick (brad) <brad@robotbattle.com> + History: 15-Jan-05, brad: split out from image.cpp + + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: jpgimage.cpp 563 2005-04-21 07:21:53Z ahuggel $"); + +// ***************************************************************************** +// included header files +#ifdef _MSC_VER +# include "exv_msvc.h" +#else +# include "exv_conf.h" +#endif + +#include "jpgimage.hpp" +#include "error.hpp" +#include "futils.hpp" + +// + standard includes +#include <cstring> +#include <cassert> + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + // Local functions. These could be static private functions on Image + // subclasses but then ImageFactory needs to be made a friend. + /*! + @brief Create a new ExvImage instance and return an auto-pointer to it. + Caller owns the returned object and the auto-pointer ensures that + it will be deleted. + */ + Image::AutoPtr newExvInstance(BasicIo::AutoPtr io, bool create); + //! Check if the file iIo is an EXV file + bool isExvType(BasicIo& iIo, bool advance); + /*! + @brief Create a new JpegImage instance and return an auto-pointer to it. + Caller owns the returned object and the auto-pointer ensures that + it will be deleted. + */ + Image::AutoPtr newJpegInstance(BasicIo::AutoPtr io, bool create); + //! Check if the file iIo is a JPEG image. + bool isJpegType(BasicIo& iIo, bool advance); + + const byte JpegBase::sos_ = 0xda; + const byte JpegBase::eoi_ = 0xd9; + const byte JpegBase::app0_ = 0xe0; + const byte JpegBase::app1_ = 0xe1; + const byte JpegBase::app13_ = 0xed; + const byte JpegBase::com_ = 0xfe; + const uint16_t JpegBase::iptc_ = 0x0404; + const char JpegBase::exifId_[] = "Exif\0\0"; + const char JpegBase::jfifId_[] = "JFIF\0"; + const char JpegBase::ps3Id_[] = "Photoshop 3.0\0"; + const char JpegBase::bimId_[] = "8BIM"; + + JpegBase::JpegBase(BasicIo::AutoPtr io, bool create, + const byte initData[], long dataSize) + : io_(io) + { + if (create) { + initImage(initData, dataSize); + } + } + + int JpegBase::initImage(const byte initData[], long dataSize) + { + if (io_->open() != 0) { + return 4; + } + IoCloser closer(*io_); + if (io_->write(initData, dataSize) != dataSize) { + return 4; + } + return 0; + } + + bool JpegBase::good() const + { + if (io_->open() != 0) return false; + IoCloser closer(*io_); + return isThisType(*io_, false); + } + + void JpegBase::clearMetadata() + { + clearIptcData(); + clearExifData(); + clearComment(); + } + + void JpegBase::clearIptcData() + { + iptcData_.clear(); + } + + void JpegBase::clearExifData() + { + exifData_.clear(); + } + + void JpegBase::clearComment() + { + comment_.erase(); + } + + void JpegBase::setExifData(const ExifData& exifData) + { + exifData_ = exifData; + } + + void JpegBase::setIptcData(const IptcData& iptcData) + { + iptcData_ = iptcData; + } + + void JpegBase::setComment(const std::string& comment) + { + comment_ = comment; + } + + void JpegBase::setMetadata(const Image& image) + { + setIptcData(image.iptcData()); + setExifData(image.exifData()); + setComment(image.comment()); + } + + int JpegBase::advanceToMarker() const + { + int c = -1; + // Skips potential padding between markers + while ((c=io_->getb()) != 0xff) { + if (c == EOF) return -1; + } + + // Markers can start with any number of 0xff + while ((c=io_->getb()) == 0xff) { + if (c == EOF) return -1; + } + return c; + } + + void JpegBase::readMetadata() + { + if (io_->open() != 0) { + throw Error(9, io_->path(), strError()); + } + IoCloser closer(*io_); + // Ensure that this is the correct image type + if (!isThisType(*io_, true)) { + if (io_->error() || io_->eof()) throw Error(14); + throw Error(15); + } + clearMetadata(); + int search = 3; + const long bufMinSize = 16; + long bufRead = 0; + DataBuf buf(bufMinSize); + + // Read section marker + int marker = advanceToMarker(); + if (marker < 0) throw Error(15); + + while (marker != sos_ && marker != eoi_ && search > 0) { + // Read size and signature (ok if this hits EOF) + bufRead = io_->read(buf.pData_, bufMinSize); + if (io_->error()) throw Error(14); + uint16_t size = getUShort(buf.pData_, bigEndian); + + if (marker == app1_ && memcmp(buf.pData_ + 2, exifId_, 6) == 0) { + if (size < 8) throw Error(15); + // Seek to begining and read the Exif data + io_->seek(8-bufRead, BasicIo::cur); + long sizeExifData = size - 8; + DataBuf rawExif(sizeExifData); + io_->read(rawExif.pData_, sizeExifData); + if (io_->error() || io_->eof()) throw Error(14); + if (exifData_.load(rawExif.pData_, sizeExifData)) throw Error(15); + --search; + } + else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) { + if (size < 16) throw Error(15); + // Read the rest of the APP13 segment + // needed if bufMinSize!=16: io_->seek(16-bufRead, BasicIo::cur); + DataBuf psData(size - 16); + io_->read(psData.pData_, psData.size_); + if (io_->error() || io_->eof()) throw Error(14); + const byte *record = 0; + uint16_t sizeIptc = 0; + uint16_t sizeHdr = 0; + // Find actual Iptc data within the APP13 segment + if (!locateIptcData(psData.pData_, psData.size_, &record, + &sizeHdr, &sizeIptc)) { + assert(sizeIptc); + if (iptcData_.load(record + sizeHdr, sizeIptc)) throw Error(15); + } + --search; + } + else if (marker == com_ && comment_.empty()) + { + if (size < 2) throw Error(15); + // Jpegs can have multiple comments, but for now only read + // the first one (most jpegs only have one anyway). Comments + // are simple single byte ISO-8859-1 strings. + io_->seek(2-bufRead, BasicIo::cur); + buf.alloc(size-2); + io_->read(buf.pData_, size-2); + if (io_->error() || io_->eof()) throw Error(14); + comment_.assign(reinterpret_cast<char*>(buf.pData_), size-2); + while ( comment_.length() + && comment_.at(comment_.length()-1) == '\0') { + comment_.erase(comment_.length()-1); + } + --search; + } + else { + if (size < 2) throw Error(15); + // Skip the remainder of the unknown segment + if (io_->seek(size-bufRead, BasicIo::cur)) throw Error(15); + } + // Read the beginning of the next segment + marker = advanceToMarker(); + if (marker < 0) throw Error(15); + } + } // JpegBase::readMetadata + + + // Operates on raw data (rather than file streams) to simplify reuse + int JpegBase::locateIptcData(const byte *pPsData, + long sizePsData, + const byte **record, + uint16_t *const sizeHdr, + uint16_t *const sizeIptc) const + { + assert(record); + assert(sizeHdr); + assert(sizeIptc); + // Used for error checking + long position = 0; + + // Data should follow Photoshop format, if not exit + while (position <= (sizePsData - 14) && + memcmp(pPsData + position, bimId_, 4)==0) { + const byte *hrd = pPsData + position; + position += 4; + uint16_t type = getUShort(pPsData+ position, bigEndian); + position += 2; + + // Pascal string is padded to have an even size (including size byte) + byte psSize = pPsData[position] + 1; + psSize += (psSize & 1); + position += psSize; + if (position >= sizePsData) return -2; + + // Data is also padded to be even + long dataSize = getULong(pPsData + position, bigEndian); + position += 4; + if (dataSize > sizePsData - position) return -2; + + if (type == iptc_) { + *sizeIptc = static_cast<uint16_t>(dataSize); + *sizeHdr = psSize + 10; + *record = hrd; + return 0; + } + position += dataSize + (dataSize & 1); + } + return 3; + } // JpegBase::locateIptcData + + void JpegBase::writeMetadata() + { + if (io_->open() != 0) { + throw Error(9, io_->path(), strError()); + } + IoCloser closer(*io_); + BasicIo::AutoPtr tempIo(io_->temporary()); // may throw + assert (tempIo.get() != 0); + + doWriteMetadata(*tempIo); // may throw + io_->close(); + io_->transfer(*tempIo); // may throw + } // JpegBase::writeMetadata + + void JpegBase::doWriteMetadata(BasicIo& outIo) + { + if (!io_->isopen()) throw Error(20); + if (!outIo.isopen()) throw Error(21); + + // Ensure that this is the correct image type + if (!isThisType(*io_, true)) { + if (io_->error() || io_->eof()) throw Error(20); + throw Error(22); + } + + const long bufMinSize = 16; + long bufRead = 0; + DataBuf buf(bufMinSize); + const long seek = io_->tell(); + int count = 0; + int search = 0; + int insertPos = 0; + int skipApp1Exif = -1; + int skipApp13Ps3 = -1; + int skipCom = -1; + DataBuf psData; + + // Write image header + if (writeHeader(outIo)) throw Error(21); + + // Read section marker + int marker = advanceToMarker(); + if (marker < 0) throw Error(22); + + // First find segments of interest. Normally app0 is first and we want + // to insert after it. But if app0 comes after com, app1 and app13 then + // don't bother. + while (marker != sos_ && marker != eoi_ && search < 3) { + // Read size and signature (ok if this hits EOF) + bufRead = io_->read(buf.pData_, bufMinSize); + if (io_->error()) throw Error(20); + uint16_t size = getUShort(buf.pData_, bigEndian); + + if (marker == app0_) { + if (size < 2) throw Error(22); + insertPos = count + 1; + if (io_->seek(size-bufRead, BasicIo::cur)) throw Error(22); + } + else if (marker == app1_ && memcmp(buf.pData_ + 2, exifId_, 6) == 0) { + if (size < 8) throw Error(22); + skipApp1Exif = count; + ++search; + if (io_->seek(size-bufRead, BasicIo::cur)) throw Error(22); + } + else if (marker == app13_ && memcmp(buf.pData_ + 2, ps3Id_, 14) == 0) { + if (size < 16) throw Error(22); + skipApp13Ps3 = count; + ++search; + // needed if bufMinSize!=16: io_->seek(16-bufRead, BasicIo::cur); + psData.alloc(size - 16); + // Load PS data now to allow reinsertion at any point + io_->read(psData.pData_, psData.size_); + if (io_->error() || io_->eof()) throw Error(20); + } + else if (marker == com_ && skipCom == -1) { + if (size < 2) throw Error(22); + // Jpegs can have multiple comments, but for now only handle + // the first one (most jpegs only have one anyway). + skipCom = count; + ++search; + if (io_->seek(size-bufRead, BasicIo::cur)) throw Error(22); + } + else { + if (size < 2) throw Error(22); + if (io_->seek(size-bufRead, BasicIo::cur)) throw Error(22); + } + marker = advanceToMarker(); + if (marker < 0) throw Error(22); + ++count; + } + + if (exifData_.count() > 0) ++search; + if (iptcData_.count() > 0) ++search; + if (!comment_.empty()) ++search; + + io_->seek(seek, BasicIo::beg); + count = 0; + marker = advanceToMarker(); + if (marker < 0) throw Error(22); + + // To simplify this a bit, new segments are inserts at either the start + // or right after app0. This is standard in most jpegs, but has the + // potential to change segment ordering (which is allowed). + // Segments are erased if there is no assigned metadata. + while (marker != sos_ && search > 0) { + // Read size and signature (ok if this hits EOF) + bufRead = io_->read(buf.pData_, bufMinSize); + if (io_->error()) throw Error(20); + // Careful, this can be a meaningless number for empty + // images with only an eoi_ marker + uint16_t size = getUShort(buf.pData_, bigEndian); + + if (insertPos == count) { + byte tmpBuf[18]; + if (!comment_.empty()) { + // Write COM marker, size of comment, and string + tmpBuf[0] = 0xff; + tmpBuf[1] = com_; + us2Data(tmpBuf + 2, + static_cast<uint16_t>(comment_.length()+3), bigEndian); + if (outIo.write(tmpBuf, 4) != 4) throw Error(21); + if (outIo.write((byte*)comment_.data(), (long)comment_.length()) + != (long)comment_.length()) throw Error(21); + if (outIo.putb(0)==EOF) throw Error(21); + if (outIo.error()) throw Error(21); + --search; + } + if (exifData_.count() > 0) { + // Write APP1 marker, size of APP1 field, Exif id and Exif data + DataBuf rawExif(exifData_.copy()); + tmpBuf[0] = 0xff; + tmpBuf[1] = app1_; + us2Data(tmpBuf + 2, + static_cast<uint16_t>(rawExif.size_+8), + bigEndian); + memcpy(tmpBuf + 4, exifId_, 6); + if (outIo.write(tmpBuf, 10) != 10) throw Error(21); + if (outIo.write(rawExif.pData_, rawExif.size_) + != rawExif.size_) throw Error(21); + if (outIo.error()) throw Error(21); + --search; + } + + const byte *record = psData.pData_; + uint16_t sizeIptc = 0; + uint16_t sizeHdr = 0; + // Safe to call with zero psData.size_ + locateIptcData(psData.pData_, psData.size_, &record, &sizeHdr, &sizeIptc); + + // Data is rounded to be even + const int sizeOldData = sizeHdr + sizeIptc + (sizeIptc & 1); + if (psData.size_ > sizeOldData || iptcData_.count() > 0) { + // rawIptc may have size of zero. + DataBuf rawIptc(iptcData_.copy()); + // write app13 marker, new size, and ps3Id + tmpBuf[0] = 0xff; + tmpBuf[1] = app13_; + const int sizeNewData = rawIptc.size_ ? + rawIptc.size_ + (rawIptc.size_ & 1) + 12 : 0; + us2Data(tmpBuf + 2, + static_cast<uint16_t>(psData.size_-sizeOldData+sizeNewData+16), + bigEndian); + memcpy(tmpBuf + 4, ps3Id_, 14); + if (outIo.write(tmpBuf, 18) != 18) throw Error(21); + if (outIo.error()) throw Error(21); + + const long sizeFront = (long)(record - psData.pData_); + const long sizeEnd = psData.size_ - sizeFront - sizeOldData; + // write data before old record. + if (outIo.write(psData.pData_, sizeFront) != sizeFront) throw Error(21); + + // write new iptc record if we have it + if (iptcData_.count() > 0) { + memcpy(tmpBuf, bimId_, 4); + us2Data(tmpBuf+4, iptc_, bigEndian); + tmpBuf[6] = 0; + tmpBuf[7] = 0; + ul2Data(tmpBuf + 8, rawIptc.size_, bigEndian); + if (outIo.write(tmpBuf, 12) != 12) throw Error(21); + if (outIo.write(rawIptc.pData_, rawIptc.size_) + != rawIptc.size_) throw Error(21); + // data is padded to be even (but not included in size) + if (rawIptc.size_ & 1) { + if (outIo.putb(0)==EOF) throw Error(21); + } + if (outIo.error()) throw Error(21); + --search; + } + + // write existing stuff after record + if (outIo.write(record+sizeOldData, sizeEnd) + != sizeEnd) throw Error(21); + if (outIo.error()) throw Error(21); + } + } + if (marker == eoi_) { + break; + } + else if (skipApp1Exif==count || skipApp13Ps3==count || skipCom==count) { + --search; + io_->seek(size-bufRead, BasicIo::cur); + } + else { + if (size < 2) throw Error(22); + buf.alloc(size+2); + io_->seek(-bufRead-2, BasicIo::cur); + io_->read(buf.pData_, size+2); + if (io_->error() || io_->eof()) throw Error(20); + if (outIo.write(buf.pData_, size+2) != size+2) throw Error(21); + if (outIo.error()) throw Error(21); + } + + // Next marker + marker = advanceToMarker(); + if (marker < 0) throw Error(22); + ++count; + } + + // Copy rest of the Io + io_->seek(-2, BasicIo::cur); + buf.alloc(4096); + long readSize = 0; + while ((readSize=io_->read(buf.pData_, buf.size_))) { + if (outIo.write(buf.pData_, readSize) != readSize) throw Error(21); + } + if (outIo.error()) throw Error(21); + + } // JpegBase::doWriteMetadata + + + const byte JpegImage::soi_ = 0xd8; + const byte JpegImage::blank_[] = { + 0xFF,0xD8,0xFF,0xDB,0x00,0x84,0x00,0x10,0x0B,0x0B,0x0B,0x0C,0x0B,0x10,0x0C,0x0C, + 0x10,0x17,0x0F,0x0D,0x0F,0x17,0x1B,0x14,0x10,0x10,0x14,0x1B,0x1F,0x17,0x17,0x17, + 0x17,0x17,0x1F,0x1E,0x17,0x1A,0x1A,0x1A,0x1A,0x17,0x1E,0x1E,0x23,0x25,0x27,0x25, + 0x23,0x1E,0x2F,0x2F,0x33,0x33,0x2F,0x2F,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x01,0x11,0x0F,0x0F,0x11,0x13,0x11,0x15,0x12, + 0x12,0x15,0x14,0x11,0x14,0x11,0x14,0x1A,0x14,0x16,0x16,0x14,0x1A,0x26,0x1A,0x1A, + 0x1C,0x1A,0x1A,0x26,0x30,0x23,0x1E,0x1E,0x1E,0x1E,0x23,0x30,0x2B,0x2E,0x27,0x27, + 0x27,0x2E,0x2B,0x35,0x35,0x30,0x30,0x35,0x35,0x40,0x40,0x3F,0x40,0x40,0x40,0x40, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0xFF,0xC0,0x00,0x11,0x08,0x00,0x01,0x00, + 0x01,0x03,0x01,0x22,0x00,0x02,0x11,0x01,0x03,0x11,0x01,0xFF,0xC4,0x00,0x4B,0x00, + 0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x07,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xDA,0x00,0x0C,0x03,0x01,0x00,0x02, + 0x11,0x03,0x11,0x00,0x3F,0x00,0xA0,0x00,0x0F,0xFF,0xD9 }; + + JpegImage::JpegImage(BasicIo::AutoPtr io, bool create) + : JpegBase(io, create, blank_, sizeof(blank_)) + { + } + + //! @cond IGNORE + JpegImage::JpegRegister::JpegRegister() + { + ImageFactory::registerImage( + Image::jpeg, newJpegInstance, isJpegType); + } + //! @endcond + + int JpegImage::writeHeader(BasicIo& outIo) const + { + // Jpeg header + byte tmpBuf[2]; + tmpBuf[0] = 0xff; + tmpBuf[1] = soi_; + if (outIo.write(tmpBuf, 2) != 2) return 4; + if (outIo.error()) return 4; + return 0; + } + + bool JpegImage::isThisType(BasicIo& iIo, bool advance) const + { + return isJpegType(iIo, advance); + } + + Image::AutoPtr newJpegInstance(BasicIo::AutoPtr io, bool create) + { + Image::AutoPtr image = Image::AutoPtr(new JpegImage(io, create)); + if (!image->good()) { + image.reset(); + } + return image; + } + + bool isJpegType(BasicIo& iIo, bool advance) + { + bool result = true; + byte tmpBuf[2]; + iIo.read(tmpBuf, 2); + if (iIo.error() || iIo.eof()) return false; + + if (0xff!=tmpBuf[0] || JpegImage::soi_!=tmpBuf[1]) { + result = false; + } + if (!advance || !result ) iIo.seek(-2, BasicIo::cur); + return result; + } + + const char ExvImage::exiv2Id_[] = "Exiv2"; + const byte ExvImage::blank_[] = { 0xff,0x01,'E','x','i','v','2',0xff,0xd9 }; + + ExvImage::ExvImage(BasicIo::AutoPtr io, bool create) + : JpegBase(io, create, blank_, sizeof(blank_)) + { + } + + //! @cond IGNORE + ExvImage::ExvRegister::ExvRegister() + { + ImageFactory::registerImage( + Image::exv, newExvInstance, isExvType); + } + //! @endcond + + int ExvImage::writeHeader(BasicIo& outIo) const + { + // Exv header + byte tmpBuf[7]; + tmpBuf[0] = 0xff; + tmpBuf[1] = 0x01; + memcpy(tmpBuf + 2, exiv2Id_, 5); + if (outIo.write(tmpBuf, 7) != 7) return 4; + if (outIo.error()) return 4; + return 0; + } + + bool ExvImage::isThisType(BasicIo& iIo, bool advance) const + { + return isExvType(iIo, advance); + } + + Image::AutoPtr newExvInstance(BasicIo::AutoPtr io, bool create) + { + Image::AutoPtr image; + if (create) { + image = Image::AutoPtr(new ExvImage(io, true)); + } + else { + image = Image::AutoPtr(new ExvImage(io, false)); + } + if (!image->good()) image.reset(); + return image; + } + + bool isExvType(BasicIo& iIo, bool advance) + { + bool result = true; + byte tmpBuf[7]; + iIo.read(tmpBuf, 7); + if (iIo.error() || iIo.eof()) return false; + + if ( 0xff != tmpBuf[0] || 0x01 != tmpBuf[1] + || memcmp(tmpBuf + 2, ExvImage::exiv2Id_, 5) != 0) { + result = false; + } + if (!advance || !result ) iIo.seek(-7, BasicIo::cur); + return result; + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/jpgimage.hpp b/src/plugins/exiv2/jpgimage.hpp @@ -0,0 +1,405 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file jpgimage.hpp + @brief Class JpegImage to access JPEG images + @version $Rev: 563 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @author Brad Schick (brad) + <a href="mailto:brad@robotbattle.com">brad@robotbattle.com</a> + @date 15-Jan-05, brad: split out from image.cpp + */ +#ifndef JPGIMAGE_HPP_ +#define JPGIMAGE_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "image.hpp" +#include "basicio.hpp" +#include "exif.hpp" +#include "iptc.hpp" + +// + standard includes +#include <string> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + + +// ***************************************************************************** +// class definitions + + /*! + @brief Abstract helper base class to access JPEG images. + */ + class JpegBase : public Image { + public: + //! @name Creators + //@{ + //! Virtual destructor. + virtual ~JpegBase() {} + //@} + //! @name Manipulators + //@{ + /*! + @brief Read all metadata from the image. Before this method + is called, the various metadata types (Iptc, Exif) will be empty. + + This method returns success even when no metadata is found in + the image. Callers must therefore check the size of individual + metadata types before accessing the data. + + @throw Error if opening or reading of the file fails or the image + data is not valid (does not look like JPEG data). + */ + void readMetadata(); + /*! + @brief Write metadata back to the image. + + All existing metadata sections in the image are either created, + replaced, or erased. If values for a given metadata type have been + assigned, a section for that metadata type will either be created or + replaced. If no values have been assigned to a given metadata type, + any exists section for that metadata type will be removed from the + image. + + @throw Error if the operation fails + */ + void writeMetadata(); + /*! + @brief Assign new exif data. The new exif data is not written + to the image until the writeMetadata() method is called. + @param exifData An ExifData instance holding exif data to be copied + */ + void setExifData(const ExifData& exifData); + void clearExifData(); + void setIptcData(const IptcData& iptcData); + void clearIptcData(); + void setComment(const std::string& comment); + void clearComment(); + void setMetadata(const Image& image); + void clearMetadata(); + //@} + + //! @name Accessors + //@{ + bool good() const; + const ExifData& exifData() const { return exifData_; } + ExifData& exifData() { return exifData_; } + const IptcData& iptcData() const { return iptcData_; } + IptcData& iptcData() { return iptcData_; } + std::string comment() const { return comment_; } + BasicIo& io() const { return *io_; } + //@} + protected: + //! @name Creators + //@{ + /*! + @brief Constructor that can either open an existing image or create + a new image from scratch. If a new image is to be created, any + existing data is overwritten. + @param io An auto-pointer that owns a BasicIo instance used for + reading and writing image metadata. \b Important: The constructor + takes ownership of the passed in BasicIo instance through the + auto-pointer. Callers should not continue to use the BasicIo + instance after it is passed to this method. Use the Image::io() + method to get a temporary reference. + @param create Specifies if an existing image should be read (false) + or if a new image should be created (true). + @param initData Data to initialize newly created images. Only used + when \em create is true. Should contain data for the smallest + valid image of the calling subclass. + @param dataSize Size of initData in bytes. + */ + JpegBase(BasicIo::AutoPtr io, bool create, + const byte initData[], long dataSize); + //@} + //! @name Manipulators + //@{ + /*! + @brief Writes the image header (aka signature) to the BasicIo instance. + @param oIo BasicIo instance that the header is written to. + @return 0 if successful;<BR> + 4 if the output file can not be written to;<BR> + */ + virtual int writeHeader(BasicIo& oIo) const =0; + //@} + //! @name Accessors + //@{ + /*! + @brief Determine if the content of the BasicIo instance is of the + type supported by this class. + + The advance flag determines if the read position in the stream is + moved (see below). This applies only if the type matches and the + function returns true. If the type does not match, the stream + position is not changed. However, if reading from the stream fails, + the stream position is undefined. Consult the stream state to obtain + more information in this case. + + @param iIo BasicIo instance to read from. + @param advance Flag indicating whether the position of the io + should be advanced by the number of characters read to + analyse the data (true) or left at its original + position (false). This applies only if the type matches. + @return true if the data matches the type of this class;<BR> + false if the data does not match;<BR> + */ + virtual bool isThisType(BasicIo& iIo, bool advance) const =0; + //@} + + // Constant Data + static const byte sos_; //!< JPEG SOS marker + static const byte eoi_; //!< JPEG EOI marker + static const byte app0_; //!< JPEG APP0 marker + static const byte app1_; //!< JPEG APP1 marker + static const byte app13_; //!< JPEG APP13 marker + static const byte com_; //!< JPEG Comment marker + static const char exifId_[]; //!< Exif identifier + static const char jfifId_[]; //!< JFIF identifier + static const char ps3Id_[]; //!< Photoshop marker + static const char bimId_[]; //!< Photoshop marker + static const uint16_t iptc_; //!< Photoshop Iptc marker + + private: + // DATA + BasicIo::AutoPtr io_; //!< Image data io pointer + ExifData exifData_; //!< Exif data container + IptcData iptcData_; //!< Iptc data container + std::string comment_; //!< JPEG comment + + // METHODS + /*! + @brief Advances associated io instance to one byte past the next + Jpeg marker and returns the marker. This method should be called + when the BasicIo instance is positioned one byte past the end of a + Jpeg segment. + @return the next Jpeg segment marker if successful;<BR> + -1 if a maker was not found before EOF;<BR> + */ + int advanceToMarker() const; + /*! + @brief Locates Photoshop formated Iptc data in a memory buffer. + Operates on raw data to simplify reuse. + @param pPsData Pointer to buffer containing entire payload of + Photoshop formated APP13 Jpeg segment. + @param sizePsData Size in bytes of pPsData. + @param record Output value that is set to the start of the Iptc + data block within pPsData (may not be null). + @param sizeHdr Output value that is set to the size of the header + within the Iptc data block pointed to by record (may not + be null). + @param sizeIptc Output value that is set to the size of the actual + Iptc data within the Iptc data block pointed to by record + (may not be null). + @return 0 if successful;<BR> + 3 if no Iptc data was found in pPsData;<BR> + -2 if the pPsData buffer does not contain valid data. + */ + int locateIptcData(const byte *pPsData, + long sizePsData, + const byte **record, + uint16_t *const sizeHdr, + uint16_t *const sizeIptc) const; + /*! + @brief Initialize the image with the provided data. + @param initData Data to be written to the associated BasicIo + @param dataSize Size in bytes of data to be written + @return 0 if successful;<BR> + 4 if the image can not be written to. + */ + int initImage(const byte initData[], long dataSize); + /*! + @brief Provides the main implementation of writeMetadata() by + writing all buffered metadata to the provided BasicIo. + @param oIo BasicIo instance to write to (a temporary location). + + @return 4 if opening or writing to the associated BasicIo fails + */ + void doWriteMetadata(BasicIo& oIo); + + // NOT Implemented + //! Default constructor. + JpegBase(); + //! Copy constructor + JpegBase(const JpegBase& rhs); + //! Assignment operator + JpegBase& operator=(const JpegBase& rhs); + }; // class JpegBase + + /*! + @brief Class to access JPEG images + */ + class JpegImage : public JpegBase { + friend bool isJpegType(BasicIo& iIo, bool advance); + public: + //! @name Creators + //@{ + /*! + @brief Constructor that can either open an existing Jpeg image or create + a new image from scratch. If a new image is to be created, any + existing data is overwritten. Since the constructor can not return + a result, callers should check the good() method after object + construction to determine success or failure. + @param io An auto-pointer that owns a BasicIo instance used for + reading and writing image metadata. \b Important: The constructor + takes ownership of the passed in BasicIo instance through the + auto-pointer. Callers should not continue to use the BasicIo + instance after it is passed to this method. Use the Image::io() + method to get a temporary reference. + @param create Specifies if an existing image should be read (false) + or if a new file should be created (true). + */ + JpegImage(BasicIo::AutoPtr io, bool create); + //! Destructor + ~JpegImage() {} + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct JpegRegister{ + JpegRegister(); + }; + //! @endcond + protected: + //! @name Accessors + //@{ + /*! + @brief Determine if the content of the BasicIo instance is a Jpeg image. + See base class for more details. + @param iIo BasicIo instance to read from. + @param advance Flag indicating whether the position of the io + should be advanced by the number of characters read to + analyse the data (true) or left at its original + position (false). This applies only if the type matches. + @return true if the data matches a Jpeg image;<BR> + false if the data does not match;<BR> + */ + bool isThisType(BasicIo& iIo, bool advance) const; + //@} + //! @name Manipulators + //@{ + /*! + @brief Writes a Jpeg header (aka signature) to the BasicIo instance. + @param oIo BasicIo instance that the header is written to. + @return 0 if successful;<BR> + 2 if the input image is invalid or can not be read;<BR> + 4 if the temporary image can not be written to;<BR> + -3 other temporary errors;<BR> + */ + int writeHeader(BasicIo& oIo) const; + //@} + private: + // Constant data + static const byte soi_; // SOI marker + static const byte blank_[]; // Minimal Jpeg image + + // NOT Implemented + //! Default constructor + JpegImage(); + //! Copy constructor + JpegImage(const JpegImage& rhs); + //! Assignment operator + JpegImage& operator=(const JpegImage& rhs); + }; // class JpegImage + + static JpegImage::JpegRegister jpegReg; + + //! Helper class to access %Exiv2 files + class ExvImage : public JpegBase { + friend bool isExvType(BasicIo& iIo, bool advance); + public: + //! @name Creators + //@{ + /*! + @brief Constructor that can either open an existing Exv image or create + a new image from scratch. If a new image is to be created, any + existing data is overwritten. Since the constructor can not return + a result, callers should check the good() method after object + construction to determine success or failure. + @param io An auto-pointer that owns a BasicIo instance used for + reading and writing image metadata. \b Important: The constructor + takes ownership of the passed in BasicIo instance through the + auto-pointer. Callers should not continue to use the BasicIo + instance after it is passed to this method. Use the Image::io() + method to get a temporary reference. + @param create Specifies if an existing image should be read (false) + or if a new file should be created (true). + */ + ExvImage(BasicIo::AutoPtr io, bool create); + //! Destructor + ~ExvImage() {} + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct ExvRegister{ + ExvRegister(); + }; + //! @endcond + protected: + //! @name Accessors + //@{ + /*! + @brief Determine if the content of the BasicIo instance is an Exv + image. See base class for more details. + @param iIo BasicIo instance to read from. + @param advance Flag indicating whether the position of the io + should be advanced by the number of characters read to + analyse the data (true) or left at its original + position (false). This applies only if the type matches. + @return true if the data matches a Jpeg image;<BR> + false if the data does not match;<BR> + */ + virtual bool isThisType(BasicIo& iIo, bool advance) const; + //@} + //! @name Manipulators + //@{ + /*! + @brief Writes an Exv header (aka signature) to the BasicIo instance. + @param oIo BasicIo instance that the header is written to. + @return 0 if successful;<BR> + 4 if the output file can not be written to;<BR> + */ + int writeHeader(BasicIo& oIo) const; + //@} + private: + // Constant data + static const char exiv2Id_[]; // Exv identifier + static const byte blank_[]; // Minimal exiv file + + // NOT Implemented + //! Default constructor + ExvImage(); + //! Copy constructor + ExvImage(const ExvImage& rhs); + //! Assignment operator + ExvImage& operator=(const ExvImage& rhs); + }; // class ExvImage + + static ExvImage::ExvRegister exvReg; +} // namespace Exiv2 + + +#endif // #ifndef JPGIMAGE_HPP_ diff --git a/src/plugins/exiv2/makernote.cpp b/src/plugins/exiv2/makernote.cpp @@ -0,0 +1,422 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: makernote.cpp + Version: $Rev: 579 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 18-Feb-04, ahu: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: makernote.cpp 579 2005-06-11 04:11:23Z ahuggel $"); + +// Define DEBUG_* to output debug information to std::cerr, e.g, by calling +// make like this: make DEFS=-DDEBUG_MAKERNOTE makernote.o +//#define DEBUG_MAKERNOTE +//#define DEBUG_REGISTRY + +// ***************************************************************************** +// included header files +#include "makernote.hpp" +#include "error.hpp" + +// + standard includes +#include <string> +#include <sstream> +#include <iomanip> +#include <iostream> +#include <cassert> + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + MakerNote::MakerNote(bool alloc) + : alloc_(alloc), offset_(0), byteOrder_(invalidByteOrder) + { + } + + MakerNote::AutoPtr MakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + MakerNote::AutoPtr MakerNote::clone() const + { + return AutoPtr(clone_()); + } + + IfdMakerNote::IfdMakerNote(IfdId ifdId, bool alloc, bool hasNext) + : MakerNote(alloc), + absOffset_(true), adjOffset_(0), ifd_(ifdId, 0, alloc, hasNext) + { + } + + IfdMakerNote::IfdMakerNote(const IfdMakerNote& rhs) + : MakerNote(rhs), absOffset_(rhs.absOffset_), adjOffset_(rhs.adjOffset_), + header_(rhs.header_.size_), ifd_(rhs.ifd_) + { + memcpy(header_.pData_, rhs.header_.pData_, header_.size_); + } + + int IfdMakerNote::read(const byte* buf, + long len, + ByteOrder byteOrder, + long offset) + { + // Remember the offset + offset_ = offset; + // Set byte order if none is set yet + if (byteOrder_ == invalidByteOrder) byteOrder_ = byteOrder; + // Read and check the header (and set offset adjustment) + int rc = readHeader(buf, len, byteOrder); + if (rc == 0) { + rc = checkHeader(); + } + // Adjust the offset + offset = absOffset_ ? offset + adjOffset_ : adjOffset_; + // Read the makernote IFD + if (rc == 0) { + rc = ifd_.read(buf + headerSize(), + len - headerSize(), + byteOrder_, + offset); + } + if (rc == 0) { + // IfdMakerNote currently does not support multiple IFDs + if (ifd_.next() != 0) { + std::cerr << "Warning: Makernote IFD has a next pointer != 0 (" + << ifd_.next() + << "). Ignored.\n"; + } + } +#ifdef DEBUG_MAKERNOTE + hexdump(std::cerr, buf, len, offset); + if (rc == 0) ifd_.print(std::cerr); +#endif + + return rc; + } // IfdMakerNote::read + + long IfdMakerNote::copy(byte* buf, ByteOrder byteOrder, long offset) + { + // Remember the new offset + offset_ = offset; + // Set byte order if none is set yet + if (byteOrder_ == invalidByteOrder) byteOrder_ = byteOrder; + // Adjust the offset + offset = absOffset_ ? offset + adjOffset_ : adjOffset_; + + long len = 0; + len += copyHeader(buf); + len += ifd_.copy(buf + len, byteOrder_, offset); + + return len; + } // IfdMakerNote::copy + + int IfdMakerNote::readHeader(const byte* buf, + long len, + ByteOrder byteOrder) + { + // Default implementation does nothing, assuming there is no header + return 0; + } + + void IfdMakerNote::updateBase(byte* pNewBase) + { + if (absOffset_) { + ifd_.updateBase(pNewBase); + } + } + + int IfdMakerNote::checkHeader() const + { + // Default implementation does nothing, assuming there is no header + return 0; + } + + long IfdMakerNote::copyHeader(byte* buf) const + { + if (header_.size_ != 0) memcpy(buf, header_.pData_, header_.size_); + return header_.size_; + } + + long IfdMakerNote::headerSize() const + { + return header_.size_; + } + + Entries::const_iterator IfdMakerNote::findIdx(int idx) const + { + return ifd_.findIdx(idx); + } + + long IfdMakerNote::size() const + { + return headerSize() + ifd_.size() + ifd_.dataSize(); + } + + IfdMakerNote::AutoPtr IfdMakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + IfdMakerNote::AutoPtr IfdMakerNote::clone() const + { + return AutoPtr(clone_()); + } + + MakerNoteFactory::Registry* MakerNoteFactory::pRegistry_ = 0; + MakerNoteFactory::IfdIdRegistry* MakerNoteFactory::pIfdIdRegistry_ = 0; + + void MakerNoteFactory::init() + { + if (0 == pRegistry_) { + pRegistry_ = new Registry; + } + if (0 == pIfdIdRegistry_) { + pIfdIdRegistry_ = new IfdIdRegistry; + } + } // MakerNoteFactory::init + + void MakerNoteFactory::registerMakerNote(IfdId ifdId, + MakerNote::AutoPtr makerNote) + { + init(); + MakerNote* pMakerNote = makerNote.release(); + assert(pMakerNote); + (*pIfdIdRegistry_)[ifdId] = pMakerNote; + } // MakerNoteFactory::registerMakerNote + + MakerNote::AutoPtr MakerNoteFactory::create(IfdId ifdId, bool alloc) + { + assert(pIfdIdRegistry_ != 0); + IfdIdRegistry::const_iterator i = pIfdIdRegistry_->find(ifdId); + if (i == pIfdIdRegistry_->end()) return MakerNote::AutoPtr(0); + assert(i->second); + return i->second->create(alloc); + } // MakerNoteFactory::create + + void MakerNoteFactory::registerMakerNote(const std::string& make, + const std::string& model, + CreateFct createMakerNote) + { +#ifdef DEBUG_REGISTRY + std::cerr << "Registering MakerNote create function for \"" + << make << "\" and \"" << model << "\".\n"; +#endif + init(); + // Todo: use case insensitive make and model comparisons + + // Find or create a registry entry for make + ModelRegistry* pModelRegistry = 0; + assert(pRegistry_ != 0); + Registry::const_iterator end1 = pRegistry_->end(); + Registry::const_iterator pos1; + for (pos1 = pRegistry_->begin(); pos1 != end1; ++pos1) { + if (pos1->first == make) break; + } + if (pos1 != end1) { + pModelRegistry = pos1->second; + } + else { + pModelRegistry = new ModelRegistry; + pRegistry_->push_back(std::make_pair(make, pModelRegistry)); + } + // Find or create a registry entry for model + ModelRegistry::iterator end2 = pModelRegistry->end(); + ModelRegistry::iterator pos2; + for (pos2 = pModelRegistry->begin(); pos2 != end2; ++pos2) { + if (pos2->first == model) break; + } + if (pos2 != end2) { + pos2->second = createMakerNote; + } + else { + pModelRegistry->push_back(std::make_pair(model, createMakerNote)); + } + } // MakerNoteFactory::registerMakerNote + + MakerNote::AutoPtr MakerNoteFactory::create(const std::string& make, + const std::string& model, + bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset) + { +#ifdef DEBUG_REGISTRY + std::cerr << "Entering MakerNoteFactory::create(\"" + << make << "\", \"" << model << "\", " + << (alloc == true ? "true" : "false") << ")\n"; +#endif + // loop through each make of the registry to find the best matching make + int score = 0; + ModelRegistry* pModelRegistry = 0; +#ifdef DEBUG_REGISTRY + std::string makeMatch; + std::cerr << "Searching make registry...\n"; +#endif + assert(pRegistry_ != 0); + Registry::const_iterator end1 = pRegistry_->end(); + Registry::const_iterator pos1; + for (pos1 = pRegistry_->begin(); pos1 != end1; ++pos1) { + int rc = match(pos1->first, make); + if (rc > score) { + score = rc; +#ifdef DEBUG_REGISTRY + makeMatch = pos1->first; +#endif + pModelRegistry = pos1->second; + } + } + if (pModelRegistry == 0) return MakerNote::AutoPtr(0); +#ifdef DEBUG_REGISTRY + std::cerr << "Best match is \"" << makeMatch << "\".\n"; +#endif + + // loop through each model of the model registry to find the best match + score = 0; + CreateFct createMakerNote = 0; +#ifdef DEBUG_REGISTRY + std::string modelMatch; + std::cerr << "Searching model registry...\n"; +#endif + ModelRegistry::const_iterator end2 = pModelRegistry->end(); + ModelRegistry::const_iterator pos2; + for (pos2 = pModelRegistry->begin(); pos2 != end2; ++pos2) { + int rc = match(pos2->first, model); + if (rc > score) { + score = rc; +#ifdef DEBUG_REGISTRY + modelMatch = pos2->first; +#endif + createMakerNote = pos2->second; + } + } + if (createMakerNote == 0) return MakerNote::AutoPtr(0); +#ifdef DEBUG_REGISTRY + std::cerr << "Best match is \"" << modelMatch << "\".\n"; +#endif + + return createMakerNote(alloc, buf, len, byteOrder, offset); + } // MakerNoteFactory::create + + int MakerNoteFactory::match(const std::string& regEntry, + const std::string& key) + { +#ifdef DEBUG_REGISTRY + std::cerr << " Matching registry entry \"" << regEntry << "\" (" + << (int)regEntry.size() << ") with key \"" << key << "\" (" + << (int)key.size() << "): "; +#endif + // Todo: make the comparisons case insensitive + + // Handle exact match (this is only necessary because of the different + // return value - the following algorithm also finds exact matches) + if (regEntry == key) { +#ifdef DEBUG_REGISTRY + std::cerr << "Exact match (score: " << (int)key.size() + 2 << ")\n"; +#endif + return static_cast<int>(key.size()) + 2; + } + std::string uKey = key; + std::string uReg = regEntry; + + int count = 0; // number of matching characters + std::string::size_type ei = 0; // index in the registry entry + std::string::size_type ki = 0; // index in the key + + while (ei != std::string::npos) { + + std::string::size_type pos = uReg.find('*', ei); + if (pos != ei) { + std::string ss = pos == std::string::npos ? + uReg.substr(ei) : uReg.substr(ei, pos - ei); + + if (ki == std::string::npos) { +#ifdef DEBUG_REGISTRY + std::cerr << "Not a match.\n"; +#endif + return 0; + } + + bool found = false; + // Find the substr ss in the key starting from index ki. + // Take care of the special cases + // + where the substr must match the key from beg to end, + // + from beg, + // + to end + // + and where it can be anywhere in the key. + // If found, ki is adjusted to the position in the key after ss. + if (ei == 0 && pos == std::string::npos) { // ei == 0 => ki == 0 + if (0 == uKey.compare(ss)) { + found = true; + ki = std::string::npos; + } + } + else if (ei == 0) { // ei == 0 => ki == 0 + if (0 == uKey.compare(0, ss.size(), ss)) { + found = true; + ki = ss.size(); + } + } + else if (pos == std::string::npos) { + if ( ss.size() <= uKey.size() + && ki <= uKey.size() - ss.size()) { + if (0 == uKey.compare( + uKey.size() - ss.size(), ss.size(), ss)) { + found = true; + ki = std::string::npos; + } + } + } + else { + std::string::size_type idx = uKey.find(ss, ki); + if (idx != std::string::npos) { + found = true; + ki = idx + ss.size(); + } + } + + if (found) { + count += static_cast<int>(ss.size()); + } + else { +#ifdef DEBUG_REGISTRY + std::cerr << "Not a match.\n"; +#endif + return 0; + } + } // if the substr is not empty + + ei = pos == std::string::npos ? std::string::npos : pos + 1; + + } // while ei doesn't point to the end of the registry entry + +#ifdef DEBUG_REGISTRY + std::cerr << "Match (score: " << count + 1 << ")\n"; +#endif + return count + 1; + + } // MakerNoteFactory::match + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/makernote.hpp b/src/plugins/exiv2/makernote.hpp @@ -0,0 +1,474 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file makernote.hpp + @brief Contains the Exif %MakerNote interface, IFD %MakerNote and a + MakerNote factory + @version $Rev: 569 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 18-Feb-04, ahu: created + */ +#ifndef MAKERNOTE_HPP_ +#define MAKERNOTE_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "ifd.hpp" + +// + standard includes +#include <string> +#include <iosfwd> +#include <utility> +#include <vector> +#include <map> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class Value; + +// ***************************************************************************** +// class definitions + + /*! + @brief Exif makernote interface + + %MakerNote is a low-level container for makernote entries. The ExifData + container uses makernote entries just like the other Exif metadata. Thus, + clients can access Exif and makernote tags and their values uniformly + through the ExifData interface. The role of %MakerNote is very similar to + that of class Ifd (but makernotes do not need to be in IFD format, see + below). In addition, it provides %MakerNote specific tag descriptions and + print functions to interpret the makernote values. + + MakerNote holds methods and functionality to + - read the makernote from a character buffer + - copy the makernote to a character buffer + - maintain a list of makernote entries (similar to IFD entries) + - interpret (print) the values of makernote tags + + Makernotes can be added to the system by subclassing %MakerNote and + registering a create function for the new subclass together with the + camera make and model (which may contain wildcards) in the + MakerNoteFactory. Since the majority of makernotes are in IFD format, + subclass IfdMakerNote is provided. It contains an IFD container and + implements all interface methods related to the makernote entries. <BR> + + To implement a new IFD makernote, all that you need to do is + - subclass %IfdMakerNote, + - implement methods to read and check the header (if any) as well as + clone and create functions, + - add a list of tag descriptions and appropriate print functions and + - register the camera make/model and create function in the makernote factory. + . + See existing makernote implementations for examples, e.g., CanonMakerNote + or FujiMakerNote. + + Finally, the header file which defines the static variable + \em register*MakerNote needs to be included from mn.hpp, to ensure that + the makernote is automatically registered in the factory. + */ + class MakerNote { + //! @name Not implemented + //@{ + //! Assignment not allowed (memory management mode alloc_ is const) + MakerNote& operator=(const MakerNote& rhs); + //@} + + public: + //! Shortcut for a %MakerNote auto pointer. + typedef std::auto_ptr<MakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to choose whether or not memory management + is required for the Entries. + */ + explicit MakerNote(bool alloc =true); + //! Virtual destructor. + virtual ~MakerNote() {} + //@} + + //! @name Manipulators + //@{ + /*! + @brief Read the makernote, including the makernote header, from + character buffer buf of length len at position offset (from the + start of the TIFF header) and encoded in byte order byteOrder. + Return 0 if successful. + */ + virtual int read(const byte* buf, + long len, + ByteOrder byteOrder, + long offset) =0; + /*! + @brief Copy (write) the makerNote to the character buffer buf at + position offset (from the start of the TIFF header), encoded + in byte order byteOrder. Update internal offsets if necessary. + Return the number of bytes written. + */ + virtual long copy(byte* buf, ByteOrder byteOrder, long offset) =0; + /*! + @brief Add the entry to the makernote. No duplicate-check is performed, + i.e., it is possible to add multiple entries with the same tag. + The memory allocation mode of the entry to be added must be the + same as that of the makernote and the IFD id of the entry must + be set to 'makerIfd'. + */ + virtual void add(const Entry& entry) =0; + //! The first makernote entry + virtual Entries::iterator begin() =0; + //! End of the makernote entries + virtual Entries::iterator end() =0; + /*! + @brief Update the base pointer of the %MakerNote and all its entries + to \em pNewBase. + + Allows to re-locate the underlying data buffer to a new location + \em pNewBase. This method only has an effect in non-alloc mode. + */ + virtual void updateBase(byte* pNewBase) =0; + //@} + + //! @name Accessors + //@{ + //! Return the byte order (little or big endian). + ByteOrder byteOrder() const { return byteOrder_; } + //! Return the offset of the makernote from the start of the TIFF header + long offset() const { return offset_; } + /*! + @brief Return an auto-pointer to an newly created, empty instance of + the same type as this. The makernote entries are <B>not</B> + copied. The caller owns the new object and the auto-pointer + ensures that it will be deleted. + + @param alloc Memory management model for the newly created object. + Indicates if memory required to store data should be allocated + and deallocated (true) or not (false). If false, only pointers + to the buffer provided to read() will be kept. See Ifd for more + background on this concept. + */ + AutoPtr create(bool alloc =true) const; + /*! + @brief Return an auto-pointer to a clone of this object. The caller + owns the new object and the auto-pointer ensures that it will + be deleted. + + @note In non-alloc mode the clone potentially contains pointers to + the same data buffer as the original. + Use updateBase(byte* pNewBase) to adjust them. + */ + AutoPtr clone() const; + //! The first makernote entry + virtual Entries::const_iterator begin() const =0; + //! End of the makernote entries + virtual Entries::const_iterator end() const =0; + //! Find an entry by idx, return a const iterator to the record + virtual Entries::const_iterator findIdx(int idx) const =0; + //! Return the size of the makernote in bytes + virtual long size() const =0; + //@} + + protected: + // DATA + /*! + @brief Flag to control the memory management: <BR> + True: requires memory allocation and deallocation, <BR> + False: no memory management needed. + */ + const bool alloc_; + /*! + @brief Offset of the makernote from the start of the TIFF header + (for offset()). + */ + long offset_; + /*! + @brief Alternative byte order to use, invalid if the byte order of the + Exif block can be used + */ + ByteOrder byteOrder_; + + private: + //! Internal virtual create function. + virtual MakerNote* create_(bool alloc =true) const =0; + //! Internal virtual copy constructor. + virtual MakerNote* clone_() const =0; + + }; // class MakerNote + + //! Type for a pointer to a function creating a makernote + typedef MakerNote::AutoPtr (*CreateFct)(bool, const byte*, long, ByteOrder, long); + + /*! + @brief Interface for MakerNotes in IFD format. See MakerNote. + */ + class IfdMakerNote : public MakerNote { + //! @name Not implemented + //@{ + //! Assignment not allowed (Ifd does not have an assignment operator) + IfdMakerNote& operator=(const IfdMakerNote& rhs); + //@} + + public: + //! Shortcut for an %IfdMakerNote auto pointer. + typedef std::auto_ptr<IfdMakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Requires an %Ifd id and allows to choose whether + or not memory management is needed for the Entries and whether + the IFD has a next pointer. + */ + explicit IfdMakerNote(IfdId ifdId, bool alloc =true, bool hasNext =true); + //! Copy constructor + IfdMakerNote(const IfdMakerNote& rhs); + //! Virtual destructor + virtual ~IfdMakerNote() {} + //@} + + //! @name Manipulators + //@{ + virtual int read(const byte* buf, + long len, + ByteOrder byteOrder, + long offset); + /*! + @brief Read the makernote header from the makernote databuffer. This + method must set the offset adjustment (adjOffset_), if needed + (assuming that the required information is in the header). + Return 0 if successful. + @note The default implementation does nothing, assuming there is no + header + */ + virtual int readHeader(const byte* buf, + long len, + ByteOrder byteOrder); + virtual long copy(byte* buf, ByteOrder byteOrder, long offset); + virtual void add(const Entry& entry) { ifd_.add(entry); } + virtual Entries::iterator begin() { return ifd_.begin(); } + virtual Entries::iterator end() { return ifd_.end(); } + virtual void updateBase(byte* pNewBase); + //@} + + //! @name Accessors + //@{ + virtual Entries::const_iterator begin() const { return ifd_.begin(); } + virtual Entries::const_iterator end() const { return ifd_.end(); } + virtual Entries::const_iterator findIdx(int idx) const; + virtual long size() const; + AutoPtr create(bool alloc =true) const; + AutoPtr clone() const; + /*! + @brief Check the makernote header. This will typically check if a + required prefix string is present in the header. Return 0 if + successful. + @note The default implementation does nothing, assuming there is no + header + */ + virtual int checkHeader() const; + /*! + @brief Write the makernote header to a character buffer, return the + number of characters written. + @note The default implementation copies the header_ buffer. + */ + virtual long copyHeader(byte* buf) const; + /*! + @brief Return the size of the makernote header in bytes. + @note The default implementation returns the size of the header_ + buffer. + */ + virtual long headerSize() const; + //@} + + protected: + // DATA + /*! + @brief True: Adjustment of the IFD offsets is to be added to the + offset from the start of the TIFF header (i.e., the + start of the Exif data section), + False: Adjustment of the IFD offsets is a suitable absolute + value. Ignore the offset from the start of the TIFF + header. + */ + bool absOffset_; + /*! + @brief Adjustment of the IFD offsets relative to the start of the + TIFF header or to the start of the makernote, depending on + the setting of absOffset_. + */ + long adjOffset_; + //! Data buffer for the makernote header + DataBuf header_; + //! The makernote IFD + Ifd ifd_; + + private: + virtual IfdMakerNote* create_(bool alloc =true) const =0; + virtual IfdMakerNote* clone_() const =0; + + }; // class IfdMakerNote + + /*! + @brief Factory for MakerNote objects. + + Maintains an associative list (tree) of camera makes/models and + corresponding %MakerNote create functions. Creates an instance of the + %MakerNote for one camera make/model. The factory is implemented as a + static class. + */ + class MakerNoteFactory { + public: + /*! + @brief Register a %MakerNote create function for a camera make and + model. + + Registers a create function for a %MakerNote for a given make and + model combination with the factory. Both the make and model strings + may contain wildcards ('*', e.g., "Canon*"). If the make already + exists in the registry, then a new branch for the model is added. If + the model also already exists, then the new create function replaces + the old one. + + @param make Camera manufacturer. (Typically the string from the Exif + make tag.) + @param model Camera model. (Typically the string from the Exif + model tag.) + @param createMakerNote Pointer to a function to create a new + %MakerNote of a particular type. + */ + static void registerMakerNote(const std::string& make, + const std::string& model, + CreateFct createMakerNote); + + //! Register a %MakerNote prototype in the IFD id registry. + static void registerMakerNote(IfdId ifdId, MakerNote::AutoPtr makerNote); + + /*! + @brief Create the appropriate %MakerNote based on camera make and + model and possibly the contents of the makernote itself, return + an auto-pointer to the newly created MakerNote instance. Return + 0 if no %MakerNote is defined for the camera model. + + The method searches the make-model tree for a make and model + combination in the registry that matches the search key. The search is + case insensitive (Todo: implement case-insensitive comparisons) and + wildcards in the registry entries are supported. First the best + matching make is searched, then the best matching model for this make + is searched. If there is no matching make or no matching model within + the models registered for the best matching make, then no makernote + is created and the function returns 0. If a match is found, the + function invokes the registered create function and returns an + auto-pointer to the newly created MakerNote. The makernote pointed to + is owned by the caller of the function and the auto-pointer ensures + that it is deleted. The best match is an exact match, then a match is + rated according to the number of matching characters. The makernote + buffer is passed on to the create function, which can based on its + content, automatically determine the correct version or flavour of the + makernote required. This is used, e.g., to determine which of the + three Nikon makernotes to create. + + @param make Camera manufacturer. (Typically the string from the Exif + make tag.) + @param model Camera model. (Typically the string from the Exif + model tag.) + @param alloc Memory management model for the new MakerNote. Determines + if memory required to store data should be allocated and + deallocated (true) or not (false). If false, only pointers to + the buffer provided to read() will be kept. See Ifd for more + background on this concept. + @param buf Pointer to the makernote character buffer. + @param len Length of the makernote character buffer. + @param byteOrder Byte order in which the Exif data (and possibly the + makernote) is encoded. + @param offset Offset from the start of the TIFF header of the makernote + buffer. + + @return An auto-pointer that owns a %MakerNote for the camera model. + If the camera is not supported, the pointer is 0. + */ + static MakerNote::AutoPtr create(const std::string& make, + const std::string& model, + bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset); + + //! Create a %MakerNote for an IFD id. + static MakerNote::AutoPtr create(IfdId ifdId, bool alloc =true); + + /*! + @brief Match a registry entry with a key (used for make and model). + + The matching algorithm is case insensitive and wildcards ('*') in the + registry entry are supported. The best match is an exact match, then + a match is rated according to the number of matching characters. + + @return A score value indicating how good the key and registry entry + match. 0 means no match, values greater than 0 indicate a + match, larger values are better matches:<BR> + 0: key and registry entry do not match<BR> + 1: a pure wildcard match, i.e., the registry entry is just + a wildcard.<BR> + Score values greater than 1 are computed by adding 1 to the + number of matching characters, except for an exact match, + which scores 2 plus the number of matching characters. + */ + static int match(const std::string& regEntry, const std::string& key); + + private: + //! @name Creators + //@{ + //! Prevent construction: not implemented. + MakerNoteFactory() {} + //! Prevent copy construction: not implemented. + MakerNoteFactory(const MakerNoteFactory& rhs); + //@} + + //! Creates the private static instance + static void init(); + + //! Type used to store model labels and %MakerNote create functions + typedef std::vector<std::pair<std::string, CreateFct> > ModelRegistry; + //! Type used to store a list of make labels and model registries + typedef std::vector<std::pair<std::string, ModelRegistry*> > Registry; + //! Type used to store a list of IFD ids and %MakerNote prototypes + typedef std::map<IfdId, MakerNote*> IfdIdRegistry; + + // DATA + //! List of makernote types and corresponding makernote create functions. + static Registry* pRegistry_; + //! List of makernote IFD ids and corresponding create functions. + static IfdIdRegistry* pIfdIdRegistry_; + + }; // class MakerNoteFactory + +} // namespace Exiv2 + +#endif // #ifndef MAKERNOTE_HPP_ diff --git a/src/plugins/exiv2/metacopy.cpp b/src/plugins/exiv2/metacopy.cpp @@ -0,0 +1,182 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + Abstract : Tester application for image file handling + + File : metacopy.cpp + Version : $Rev: 560 $ + Author(s): Brad Schick (brad) <brad@robotbattle.com> + History : 13-Jul-04, brad: created + */ +// ***************************************************************************** +// included header files +#include "image.hpp" +#include "iptc.hpp" +#include "exif.hpp" +#include "types.hpp" +#include "metacopy.hpp" +#include <iostream> +#include <fstream> +#include <cassert> + +// ***************************************************************************** +// Main +int main(int argc, char* const argv[]) +{ +try { + // Handle command line arguments + Params params; + if (params.getopt(argc, argv)) { + params.usage(); + return 1; + } + if (params.help_) { + params.help(); + return 2; + } + + // Use MemIo to increase test coverage. + Exiv2::BasicIo::AutoPtr fileIo(new Exiv2::FileIo(params.read_)); + Exiv2::BasicIo::AutoPtr memIo(new Exiv2::MemIo); + memIo->transfer(*fileIo); + + Exiv2::Image::AutoPtr readImg = Exiv2::ImageFactory::open(memIo); + assert(readImg.get() != 0); + readImg->readMetadata(); + + Exiv2::Image::AutoPtr writeImg = Exiv2::ImageFactory::open(params.write_); + assert(writeImg.get() != 0); + if (params.preserve_) writeImg->readMetadata(); + if (params.iptc_) { + writeImg->setIptcData(readImg->iptcData()); + } + if (params.exif_) { + writeImg->setExifData(readImg->exifData()); + } + if (params.comment_) { + writeImg->setComment(readImg->comment()); + } + + try { + writeImg->writeMetadata(); + } + catch (const Exiv2::AnyError&) { + std::cerr << params.progname() << + ": Could not write metadata to (" << params.write_ << ")\n"; + return 8; + } + + return 0; +} +catch (Exiv2::AnyError& e) { + std::cerr << "Caught Exiv2 exception '" << e << "'\n"; + return 10; +} +} + +int Params::option(int opt, const std::string& optarg, int optopt) +{ + int rc = 0; + switch (opt) { + case 'h': help_ = true; break; + case 'i': iptc_ = true; break; + case 'e': exif_ = true; break; + case 'c': comment_ = true; break; + case 'p': preserve_ = true; break; + case 'a': + iptc_ =true; + exif_ =true; + comment_ =true; + break; + case ':': + std::cerr << progname() << ": Option -" << static_cast<char>(optopt) + << " requires an argument\n"; + rc = 1; + break; + case '?': + std::cerr << progname() << ": Unrecognized option -" + << static_cast<char>(optopt) << "\n"; + rc = 1; + break; + default: + std::cerr << progname() + << ": getopt returned unexpected character code " + << std::hex << opt << "\n"; + rc = 1; + break; + } + + return rc; +} + +int Params::nonoption(const std::string& argv) +{ + if (!write_.empty()) { + std::cerr << progname() << ": Unexpected extra argument (" << argv << ")\n"; + return 1; + } + if (first_) read_ = argv; + else write_ = argv; + first_ = false; + return 0; +} + +int Params::getopt(int argc, char* const argv[]) +{ + int rc = Util::Getopt::getopt(argc, argv, optstring_); + // Further consistency checks + if (help_==false) { + if (rc==0 && read_.empty() ) { + std::cerr << progname() << ": Read and write files must be specified\n"; + rc = 1; + } + if (rc==0 && write_.empty() ) { + std::cerr << progname() << ": Write file must be specified\n"; + rc = 1; + } + if (preserve_ && iptc_ && exif_ && comment_ ) { + std::cerr << progname() << ": Option -p has no effect when all metadata types are specified.\n"; + rc = 1; + } + } + return rc; +} // Params::getopt + + +void Params::usage(std::ostream& os) const +{ + os << "\nReads and writes raw metadata. Use -h option for help.\n" + << "Usage: " << progname() + << " [-iecaph] readfile writefile\n"; +} + +void Params::help(std::ostream& os) const +{ + usage(os); + os << "\nOptions:\n" + << " -i Read Iptc data from readfile and write to writefile.\n" + << " -e Read Exif data from readfile and write to writefile.\n" + << " -c Read Jpeg comment from readfile and write to writefile.\n" + << " -a Read all metadata from readfile and write to writefile.\n" + << " -p Preserve existing metadata in writefile if not replaced.\n" + << " -h Display this help and exit.\n\n"; +} // Params::help + diff --git a/src/plugins/exiv2/metacopy.hpp b/src/plugins/exiv2/metacopy.hpp @@ -0,0 +1,86 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file metacopy.hpp + @brief Defines class Params, used for the command line handling + @version $Rev: 538 $ + @author Brad Schick (brad) <brad@robotbattle.com> + @date 13-Jul-04, brad: created + */ +#ifndef METACOPY_HPP_ +#define METACOPY_HPP_ + +#include "utils.hpp" + +class Params : public Util::Getopt { +private: + std::string optstring_; + bool first_; + +public: + bool help_; //!< Help option flag. + bool iptc_; //!< Iptc option flag. + bool exif_; //!< Exif option flag. + bool comment_; //!< JPEG comment option flag. + bool preserve_; //!< Preserve existing metadata option flag. + std::string read_; //!< Source file + std::string write_; //!< Destination file + +public: + /*! + @brief Default constructor. Note that optstring_ is initialized here. + */ + Params() : optstring_(":iecaph"), + first_(true), + help_(false), + iptc_(false), + exif_(false), + comment_(false), + preserve_(false) + {} + + /*! + @brief Call Getopt::getopt() with optstring, to initiate command line + argument parsing, perform consistency checks after all command line + arguments are parsed. + + @param argc Argument count as passed to main() on program invocation. + @param argv Argument array as passed to main() on program invocation. + + @return 0 if successful, >0 in case of errors. + */ + int getopt(int argc, char* const argv[]); + + //! Handle options and their arguments. + virtual int option(int opt, const std::string& optarg, int optopt); + + //! Handle non-option parameters. + virtual int nonoption(const std::string& argv); + + //! Print a minimal usage note to an output stream. + void usage(std::ostream& os =std::cout) const; + + //! Print further usage explanations to an output stream. + void help(std::ostream& os =std::cout) const; + +}; // class Params + +#endif // METACOPY_HPP_ diff --git a/src/plugins/exiv2/metadatum.cpp b/src/plugins/exiv2/metadatum.cpp @@ -0,0 +1,76 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: metadatum.cpp + Version: $Rev: 538 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + Brad Schick (brad) <brad@robotbattle.com> + History: 26-Jan-04, ahu: created + 31-Jul-04, brad: isolated as a component + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: metadatum.cpp 538 2005-03-12 16:43:06Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "metadatum.hpp" + +// + standard includes +#include <iostream> +#include <iomanip> + + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + Key::AutoPtr Key::clone() const + { + return AutoPtr(clone_()); + } + + std::ostream& operator<<(std::ostream& os, const Metadatum& md) + { + os << "0x" << std::setw(4) << std::setfill('0') << std::right + << std::hex << md.tag() << " " + << std::setw(40) << std::setfill(' ') << std::left + << md.key() << " " + << std::setw(9) << std::setfill(' ') << std::left + << md.typeName() << " " + << std::dec << md.value() + << "\n"; + return os; + } + + bool cmpMetadataByTag(const Metadatum& lhs, const Metadatum& rhs) + { + return lhs.tag() < rhs.tag(); + } + + + bool cmpMetadataByKey(const Metadatum& lhs, const Metadatum& rhs) + { + return lhs.key() < rhs.key(); + } + +} // namespace Exiv2 + diff --git a/src/plugins/exiv2/metadatum.hpp b/src/plugins/exiv2/metadatum.hpp @@ -0,0 +1,294 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file metadatum.hpp + @brief Provides abstract base classes Metadatum and Key + @version $Rev: 560 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @author Brad Schick (brad) + <a href="mailto:brad@robotbattle.com">brad@robotbattle.com</a> + @date 09-Jan-04, ahu: created<BR> + 31-Jul-04, brad: isolated as a component<BR> + 23-Aug-04, ahu: added Key + */ +#ifndef METADATUM_HPP_ +#define METADATUM_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "value.hpp" + +// + standard includes +#include <string> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class definitions + + /*! + @brief Abstract base class defining the %Key of a metadatum. + Keys are used to identify and group metadata. + */ + class Key { + public: + //! Shortcut for a %Key auto pointer. + typedef std::auto_ptr<Key> AutoPtr; + + //! @name Creators + //@{ + //! Destructor + virtual ~Key() {} + //@} + + //! @name Accessors + //@{ + /*! + @brief Return the key of the metadatum as a string. The key is of the + form 'familyName.groupName.tagName'. Note however that the + key is not necessarily unique, e.g., an ExifData may contain + multiple metadata with the same key. + */ + virtual std::string key() const =0; + //! Return an identifier for the type of metadata (the first part of the key) + virtual const char* familyName() const =0; + //! Return the name of the group (the second part of the key) + virtual std::string groupName() const =0; + //! Return the name of the tag (which is also the third part of the key) + virtual std::string tagName() const =0; + //! Return the tag number + virtual uint16_t tag() const =0; + /*! + @brief Return an auto-pointer to a copy of itself (deep copy). + The caller owns this copy and the auto-pointer ensures that it + will be deleted. + */ + AutoPtr clone() const; + /*! + @brief Write the key to an output stream. You do not usually have + to use this function; it is used for the implementation of + the output operator for %Key, + operator<<(std::ostream &os, const Key &key). + */ + std::ostream& write(std::ostream& os) const { return os << key(); } + //@} + + protected: + //! @name Manipulators + //@{ + /*! + @brief Assignment operator. Protected so that it can only be used + by subclasses but not directly. + */ + Key& operator=(const Key& rhs) { return *this; } + //@} + + private: + //! Internal virtual copy constructor. + virtual Key* clone_() const =0; + + }; // class Key + + //! Output operator for Key types + inline std::ostream& operator<<(std::ostream& os, const Key& key) + { + return key.write(os); + } + + /*! + @brief Abstract base class defining the interface to access information + related to one metadata tag. + */ + class Metadatum { + public: + //! @name Creators + //@{ + //! Default Constructor + Metadatum() {} + //! Copy constructor + Metadatum(const Metadatum& rhs) {} + //! Destructor + virtual ~Metadatum() {} + //@} + + //! @name Manipulators + //@{ + /*! + @brief Set the value. This method copies (clones) the value pointed + to by pValue. + */ + virtual void setValue(const Value* pValue) =0; + /*! + @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. + */ + virtual void setValue(const std::string& buf) =0; + //@} + + //! @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. + */ + virtual long copy(byte* buf, ByteOrder byteOrder) const =0; + /*! + @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. + */ + virtual std::string key() const =0; + //! Return the name of the tag (which is also the third part of the key) + virtual std::string tagName() const =0; + //! Return the tag + virtual uint16_t tag() const =0; + //! Return the type id of the value + virtual TypeId typeId() const =0; + //! Return the name of the type + virtual const char* typeName() const =0; + //! Return the size in bytes of one component of this type + virtual long typeSize() const =0; + //! Return the number of components in the value + virtual long count() const =0; + //! Return the size of the value in bytes + virtual long size() const =0; + //! 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. + */ + 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. + */ + 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. + */ + virtual Rational toRational(long n =0) const =0; + /*! + @brief Return an auto-pointer to a copy (clone) of the value. The + caller owns this copy and the auto-poiner 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 containing a pointer to a copy (clone) of the + value, 0 if the value is not set. + */ + virtual Value::AutoPtr getValue() const =0; + /*! + @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. + */ + virtual const Value& value() const =0; + //@} + + protected: + //! @name Manipulators + //@{ + /*! + @brief Assignment operator. Protected so that it can only be used + by subclasses but not directly. + */ + Metadatum& operator=(const Metadatum& rhs) { return *this; } + //@} + + }; // class Metadatum + + //! Unary predicate that matches a Exifdatum with a given key + class FindMetadatumByKey { + public: + //! Constructor, initializes the object with the tag to look for + FindMetadatumByKey(const std::string& key) : key_(key) {} + /*! + @brief Returns true if the key of the argument metadatum is equal + to that of the object. + */ + bool operator()(const Metadatum& metadatum) const + { return key_ == metadatum.key(); } + + private: + std::string key_; + + }; // class FindMetadatumByTag + + + /*! + @brief Output operator for Metadatum types, printing the interpreted + tag value. + */ + std::ostream& operator<<(std::ostream& os, const Metadatum& md); + /*! + @brief Compare two metadata by tag. Return true if the tag of metadatum + lhs is less than that of rhs. + */ + bool cmpMetadataByTag(const Metadatum& lhs, const Metadatum& rhs); + /*! + @brief Compare two metadata by key. Return true if the key of metadatum + lhs is less than that of rhs. + */ + bool cmpMetadataByKey(const Metadatum& lhs, const Metadatum& rhs); + +} // namespace Exiv2 + +#endif // #ifndef METADATUM_HPP_ diff --git a/src/plugins/exiv2/mn.hpp b/src/plugins/exiv2/mn.hpp @@ -0,0 +1,43 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file mn.hpp + @brief Include all makernote header files. Makes sure that the static + variable used to register makernotes is instantiated. + @version $Rev: 581 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 28-May-05, ahu: created + */ +#ifndef MN_HPP_ +#define MN_HPP_ + +// ***************************************************************************** +// included header files +#include "canonmn.hpp" +#include "fujimn.hpp" +#include "nikonmn.hpp" +#include "olympusmn.hpp" +#include "panasonicmn.hpp" +#include "sigmamn.hpp" +#include "sonymn.hpp" + +#endif // #ifndef MN_HPP_ diff --git a/src/plugins/exiv2/nikonmn.cpp b/src/plugins/exiv2/nikonmn.cpp @@ -0,0 +1,870 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 Andreas Huggel <ahuggel@gmx.net> + * + * Lens database to decode Exif.Nikon3.LensData + * Copyright (C) 2005 Robert Rottmerhusen <email@rottmerhusen.com> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: nikonmn.cpp + Version: $Rev: 588 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 17-May-04, ahu: created + 25-May-04, ahu: combined all Nikon formats in one component + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: nikonmn.cpp 588 2005-06-14 13:57:39Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "nikonmn.hpp" +#include "makernote.hpp" +#include "value.hpp" +#include "image.hpp" +#include "tags.hpp" +#include "error.hpp" + +// + standard includes +#include <string> +#include <sstream> +#include <iomanip> +#include <cassert> +#include <cstring> + +#define EXV_HAVE_LENSDATA + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + //! @cond IGNORE + Nikon1MakerNote::RegisterMn::RegisterMn() + { + MakerNoteFactory::registerMakerNote("NIKON*", "*", createNikonMakerNote); + MakerNoteFactory::registerMakerNote( + nikon1IfdId, MakerNote::AutoPtr(new Nikon1MakerNote)); + + ExifTags::registerMakerTagInfo(nikon1IfdId, tagInfo_); + } + //! @endcond + + // Nikon1 MakerNote Tag Info + const TagInfo Nikon1MakerNote::tagInfo_[] = { + TagInfo(0x0001, "Version", "Nikon Makernote version", nikon1IfdId, makerTags, undefined, printValue), + TagInfo(0x0002, "ISOSpeed", "ISO speed setting", nikon1IfdId, makerTags, unsignedShort, print0x0002), + TagInfo(0x0003, "ColorMode", "Color mode", nikon1IfdId, makerTags, asciiString, printValue), + TagInfo(0x0004, "Quality", "Image quality setting", nikon1IfdId, makerTags, asciiString, printValue), + TagInfo(0x0005, "WhiteBalance", "White balance", nikon1IfdId, makerTags, asciiString, printValue), + TagInfo(0x0006, "Sharpening", "Image sharpening setting", nikon1IfdId, makerTags, asciiString, printValue), + TagInfo(0x0007, "Focus", "Focus mode", nikon1IfdId, makerTags, asciiString, print0x0007), + TagInfo(0x0008, "Flash", "Flash mode", nikon1IfdId, makerTags, asciiString, printValue), + TagInfo(0x000a, "0x000a", "Unknown", nikon1IfdId, makerTags, unsignedRational, printValue), + TagInfo(0x000f, "ISOSelection", "ISO selection", nikon1IfdId, makerTags, asciiString, printValue), + TagInfo(0x0010, "DataDump", "Data dump", nikon1IfdId, makerTags, undefined, printValue), + TagInfo(0x0080, "ImageAdjustment", "Image adjustment setting", nikon1IfdId, makerTags, asciiString, printValue), + TagInfo(0x0082, "Adapter", "Adapter used", nikon1IfdId, makerTags, asciiString, printValue), + TagInfo(0x0085, "FocusDistance", "Manual focus distance", nikon1IfdId, makerTags, unsignedRational, print0x0085), + TagInfo(0x0086, "DigitalZoom", "Digital zoom setting", nikon1IfdId, makerTags, unsignedRational, print0x0086), + TagInfo(0x0088, "AFFocusPos", "AF focus position", nikon1IfdId, makerTags, undefined, print0x0088), + // End of list marker + TagInfo(0xffff, "(UnknownNikon1MnTag)", "Unknown Nikon1MakerNote tag", nikon1IfdId, makerTags, invalidTypeId, printValue) + }; + + Nikon1MakerNote::Nikon1MakerNote(bool alloc) + : IfdMakerNote(nikon1IfdId, alloc) + { + } + + Nikon1MakerNote::Nikon1MakerNote(const Nikon1MakerNote& rhs) + : IfdMakerNote(rhs) + { + } + + Nikon1MakerNote::AutoPtr Nikon1MakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + Nikon1MakerNote* Nikon1MakerNote::create_(bool alloc) const + { + return new Nikon1MakerNote(alloc); + } + + Nikon1MakerNote::AutoPtr Nikon1MakerNote::clone() const + { + return AutoPtr(clone_()); + } + + Nikon1MakerNote* Nikon1MakerNote::clone_() const + { + return new Nikon1MakerNote(*this); + } + + std::ostream& Nikon1MakerNote::print0x0002(std::ostream& os, + const Value& value) + { + if (value.count() > 1) { + os << value.toLong(1); + } + else { + os << "(" << value << ")"; + } + return os; + } + + std::ostream& Nikon1MakerNote::print0x0007(std::ostream& os, + const Value& value) + { + std::string focus = value.toString(); + if (focus == "AF-C ") os << "Continuous autofocus"; + else if (focus == "AF-S ") os << "Single autofocus"; + else os << "(" << value << ")"; + return os; + } + + std::ostream& Nikon1MakerNote::print0x0085(std::ostream& os, + const Value& value) + { + Rational distance = value.toRational(); + if (distance.first == 0) { + os << "Unknown"; + } + else if (distance.second != 0) { + std::ostringstream oss; + oss.copyfmt(os); + os << std::fixed << std::setprecision(2) + << (float)distance.first / distance.second + << " m"; + os.copyfmt(oss); + } + else { + os << "(" << value << ")"; + } + return os; + } + + std::ostream& Nikon1MakerNote::print0x0086(std::ostream& os, + const Value& value) + { + Rational zoom = value.toRational(); + if (zoom.first == 0) { + os << "Not used"; + } + else if (zoom.second != 0) { + std::ostringstream oss; + oss.copyfmt(os); + os << std::fixed << std::setprecision(1) + << (float)zoom.first / zoom.second + << "x"; + os.copyfmt(oss); + } + else { + os << "(" << value << ")"; + } + return os; + } + + std::ostream& Nikon1MakerNote::print0x0088(std::ostream& os, + const Value& value) + { + if (value.count() > 1) { + switch (value.toLong(1)) { + case 0: os << "Center"; break; + case 1: os << "Top"; break; + case 2: os << "Bottom"; break; + case 3: os << "Left"; break; + case 4: os << "Right"; break; + default: os << "(" << value << ")"; break; + } + } + else { + os << "(" << value << ")"; + } + return os; + } + + //! @cond IGNORE + Nikon2MakerNote::RegisterMn::RegisterMn() + { + MakerNoteFactory::registerMakerNote( + nikon2IfdId, MakerNote::AutoPtr(new Nikon2MakerNote)); + + ExifTags::registerMakerTagInfo(nikon2IfdId, tagInfo_); + } + //! @endcond + + // Nikon2 MakerNote Tag Info + const TagInfo Nikon2MakerNote::tagInfo_[] = { + TagInfo(0x0002, "0x0002", "Unknown", nikon2IfdId, makerTags, asciiString, printValue), + TagInfo(0x0003, "Quality", "Image quality setting", nikon2IfdId, makerTags, unsignedShort, print0x0003), + TagInfo(0x0004, "ColorMode", "Color mode", nikon2IfdId, makerTags, unsignedShort, print0x0004), + TagInfo(0x0005, "ImageAdjustment", "Image adjustment setting", nikon2IfdId, makerTags, unsignedShort, print0x0005), + TagInfo(0x0006, "ISOSpeed", "ISO speed setting", nikon2IfdId, makerTags, unsignedShort, print0x0006), + TagInfo(0x0007, "WhiteBalance", "White balance", nikon2IfdId, makerTags, unsignedShort, print0x0007), + TagInfo(0x0008, "Focus", "Focus mode", nikon2IfdId, makerTags, unsignedRational, printValue), + TagInfo(0x0009, "0x0009", "Unknown", nikon2IfdId, makerTags, asciiString, printValue), + TagInfo(0x000a, "DigitalZoom", "Digital zoom setting", nikon2IfdId, makerTags, unsignedRational, print0x000a), + TagInfo(0x000b, "Adapter", "Adapter used", nikon2IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0f00, "0x0f00", "Unknown", nikon2IfdId, makerTags, unsignedLong, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownNikon2MnTag)", "Unknown Nikon2MakerNote tag", nikon2IfdId, makerTags, invalidTypeId, printValue) + }; + + Nikon2MakerNote::Nikon2MakerNote(bool alloc) + : IfdMakerNote(nikon2IfdId, alloc) + { + byte buf[] = { + 'N', 'i', 'k', 'o', 'n', '\0', 0x00, 0x01 + }; + readHeader(buf, 8, byteOrder_); + } + + Nikon2MakerNote::Nikon2MakerNote(const Nikon2MakerNote& rhs) + : IfdMakerNote(rhs) + { + } + + int Nikon2MakerNote::readHeader(const byte* buf, + long len, + ByteOrder byteOrder) + { + if (len < 8) return 1; + + header_.alloc(8); + memcpy(header_.pData_, buf, header_.size_); + adjOffset_ = 8; + return 0; + } + + int Nikon2MakerNote::checkHeader() const + { + int rc = 0; + // Check the Nikon prefix + if ( header_.size_ < 8 + || std::string(reinterpret_cast<char*>(header_.pData_), 6) + != std::string("Nikon\0", 6)) { + rc = 2; + } + return rc; + } + + Nikon2MakerNote::AutoPtr Nikon2MakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + Nikon2MakerNote* Nikon2MakerNote::create_(bool alloc) const + { + AutoPtr makerNote(new Nikon2MakerNote(alloc)); + assert(makerNote.get() != 0); + makerNote->readHeader(header_.pData_, header_.size_, byteOrder_); + return makerNote.release(); + } + + Nikon2MakerNote::AutoPtr Nikon2MakerNote::clone() const + { + return AutoPtr(clone_()); + } + + Nikon2MakerNote* Nikon2MakerNote::clone_() const + { + return new Nikon2MakerNote(*this); + } + + std::ostream& Nikon2MakerNote::print0x0003(std::ostream& os, + const Value& value) + { + long quality = value.toLong(); + switch (quality) { + case 1: os << "VGA Basic"; break; + case 2: os << "VGA Normal"; break; + case 3: os << "VGA Fine"; break; + case 4: os << "SXGA Basic"; break; + case 5: os << "SXGA Normal"; break; + case 6: os << "SXGA Fine"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& Nikon2MakerNote::print0x0004(std::ostream& os, + const Value& value) + { + long color = value.toLong(); + switch (color) { + case 1: os << "Color"; break; + case 2: os << "Monochrome"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& Nikon2MakerNote::print0x0005(std::ostream& os, + const Value& value) + { + long adjustment = value.toLong(); + switch (adjustment) { + case 0: os << "Normal"; break; + case 1: os << "Bright+"; break; + case 2: os << "Bright-"; break; + case 3: os << "Contrast+"; break; + case 4: os << "Contrast-"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& Nikon2MakerNote::print0x0006(std::ostream& os, + const Value& value) + { + long iso = value.toLong(); + switch (iso) { + case 0: os << "80"; break; + case 2: os << "160"; break; + case 4: os << "320"; break; + case 5: os << "100"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& Nikon2MakerNote::print0x0007(std::ostream& os, + const Value& value) + { + long wb = value.toLong(); + switch (wb) { + case 0: os << "Auto"; break; + case 1: os << "Preset"; break; + case 2: os << "Daylight"; break; + case 3: os << "Incandescent"; break; + case 4: os << "Fluorescent"; break; + case 5: os << "Cloudy"; break; + case 6: os << "Speedlight"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& Nikon2MakerNote::print0x000a(std::ostream& os, + const Value& value) + { + Rational zoom = value.toRational(); + if (zoom.first == 0) { + os << "Not used"; + } + else if (zoom.second != 0) { + std::ostringstream oss; + oss.copyfmt(os); + os << std::fixed << std::setprecision(1) + << (float)zoom.first / zoom.second + << "x"; + os.copyfmt(oss); + } + else { + os << "(" << value << ")"; + } + return os; + } + + //! @cond IGNORE + Nikon3MakerNote::RegisterMn::RegisterMn() + { + MakerNoteFactory::registerMakerNote( + nikon3IfdId, MakerNote::AutoPtr(new Nikon3MakerNote)); + + ExifTags::registerMakerTagInfo(nikon3IfdId, tagInfo_); + } + //! @endcond + + // Nikon3 MakerNote Tag Info + const TagInfo Nikon3MakerNote::tagInfo_[] = { + TagInfo(0x0001, "Version", "Nikon Makernote version", nikon3IfdId, makerTags, undefined, printValue), + TagInfo(0x0002, "ISOSpeed", "ISO speed used", nikon3IfdId, makerTags, unsignedShort, print0x0002), + TagInfo(0x0003, "ColorMode", "Color mode", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0004, "Quality", "Image quality setting", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0005, "WhiteBalance", "White balance", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0006, "Sharpening", "Image sharpening setting", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0007, "Focus", "Focus mode", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0008, "FlashSetting", "Flash setting", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0009, "FlashMode", "Flash mode", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x000b, "WhiteBalanceBias", "White balance bias", nikon3IfdId, makerTags, signedShort, printValue), +// TagInfo(0x000c, "ColorBalance1", "Color balance 1", nikon3IfdId, makerTags, xxx, printValue), + TagInfo(0x000d, "0x000d", "Unknown", nikon3IfdId, makerTags, undefined, printValue), + TagInfo(0x000e, "ExposureDiff", "Exposure difference", nikon3IfdId, makerTags, undefined, printValue), + TagInfo(0x000f, "ISOSelection", "ISO selection", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0010, "DataDump", "Data dump", nikon3IfdId, makerTags, undefined, printValue), + TagInfo(0x0011, "ThumbOffset", "Thumbnail IFD offset", nikon3IfdId, makerTags, unsignedLong, printValue), + TagInfo(0x0012, "FlashComp", "Flash compensation setting", nikon3IfdId, makerTags, undefined, print0x0012), + TagInfo(0x0013, "ISOSetting", "ISO speed setting", nikon3IfdId, makerTags, unsignedShort, print0x0002), // use 0x0002 print fct + TagInfo(0x0016, "ImageBoundry", "Image boundry", nikon3IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0017, "0x0017", "Unknown", nikon3IfdId, makerTags, undefined, printValue), + TagInfo(0x0018, "FlashBracketComp", "Flash bracket compensation applied", nikon3IfdId, makerTags, undefined, print0x0012), // use 0x0012 print fct + TagInfo(0x0019, "ExposureBracketComp", "AE bracket compensation applied", nikon3IfdId, makerTags, signedRational, printValue), + TagInfo(0x0080, "ImageAdjustment", "Image adjustment setting", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0081, "ToneComp", "Tone compensation setting (contrast)", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0082, "AuxiliaryLens", "Auxiliary lens (adapter)", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0083, "LensType", "Lens type", nikon3IfdId, makerTags, unsignedByte, printValue), + TagInfo(0x0084, "Lens", "Lens", nikon3IfdId, makerTags, unsignedRational, print0x0084), + TagInfo(0x0085, "FocusDistance", "Manual focus distance", nikon3IfdId, makerTags, unsignedRational, printValue), + TagInfo(0x0086, "DigitalZoom", "Digital zoom setting", nikon3IfdId, makerTags, unsignedRational, printValue), + TagInfo(0x0087, "FlashType", "Type of flash used", nikon3IfdId, makerTags, unsignedByte, print0x0087), + TagInfo(0x0088, "AFFocusPos", "AF focus position", nikon3IfdId, makerTags, undefined, print0x0088), + TagInfo(0x0089, "Bracketing", "Bracketing", nikon3IfdId, makerTags, unsignedShort, print0x0089), + TagInfo(0x008a, "0x008a", "Unknown", nikon3IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x008b, "LensFStops", "Number of lens stops", nikon3IfdId, makerTags, undefined, print0x008b), +// TagInfo(0x008c, "NEFCurve1", "NEF curve 1", nikon3IfdId, makerTags, xxx, printValue), + TagInfo(0x008d, "ColorMode", "Color mode", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x008f, "SceneMode", "Scene mode", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0090, "LightingType", "Lighting type", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x0091, "0x0091", "Unknown", nikon3IfdId, makerTags, undefined, printValue), + TagInfo(0x0092, "HueAdjustment", "Hue adjustment", nikon3IfdId, makerTags, signedShort, printValue), + TagInfo(0x0094, "Saturation", "Saturation adjustment", nikon3IfdId, makerTags, signedShort, printValue), + TagInfo(0x0095, "NoiseReduction", "Noise reduction", nikon3IfdId, makerTags, asciiString, printValue), +// TagInfo(0x0096, "NEFCurve2", "NEF curve 2", nikon3IfdId, makerTags, xxx, printValue), + TagInfo(0x0097, "ColorBalance2", "Color balance 2", nikon3IfdId, makerTags, undefined, printValue), + TagInfo(0x0098, "LensData", "Lens data", nikon3IfdId, makerTags, undefined, print0x0098), + TagInfo(0x0099, "NEFThumbnailSize", "NEF thumbnail size", nikon3IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x009a, "0x009a", "Unknown", nikon3IfdId, makerTags, unsignedRational, printValue), + TagInfo(0x009b, "0x009b", "Unknown", nikon3IfdId, makerTags, unsignedShort, printValue), + TagInfo(0x009f, "0x009f", "Unknown", nikon3IfdId, makerTags, signedShort, printValue), + TagInfo(0x00a0, "SerialNumber", "Camera serial number", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x00a2, "0x00a2", "Unknown", nikon3IfdId, makerTags, unsignedLong, printValue), + TagInfo(0x00a3, "0x00a3", "Unknown", nikon3IfdId, makerTags, unsignedByte, printValue), + TagInfo(0x00a5, "0x00a5", "Unknown", nikon3IfdId, makerTags, unsignedLong, printValue), + TagInfo(0x00a6, "0x00a6", "Unknown", nikon3IfdId, makerTags, unsignedLong, printValue), + TagInfo(0x00a7, "ShutterCount", "Number of shots taken by camera", nikon3IfdId, makerTags, unsignedLong, printValue), + TagInfo(0x00a8, "0x00a8", "Unknown", nikon3IfdId, makerTags, undefined, printValue), + TagInfo(0x00a9, "ImageOptimization", "Image optimization", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x00aa, "Saturation", "Saturation", nikon3IfdId, makerTags, asciiString, printValue), + TagInfo(0x00ab, "VariProgram", "Vari program", nikon3IfdId, makerTags, asciiString, printValue), +// TagInfo(0x0e00, "PrintIM", "Print image matching", nikon3IfdId, makerTags, xxx, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownNikon3MnTag)", "Unknown Nikon3MakerNote tag", nikon3IfdId, makerTags, invalidTypeId, printValue) + }; + + Nikon3MakerNote::Nikon3MakerNote(bool alloc) + : IfdMakerNote(nikon3IfdId, alloc) + { + absOffset_ = false; + byte buf[] = { + 'N', 'i', 'k', 'o', 'n', '\0', + 0x02, 0x10, 0x00, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08 + }; + readHeader(buf, 18, byteOrder_); + } + + Nikon3MakerNote::Nikon3MakerNote(const Nikon3MakerNote& rhs) + : IfdMakerNote(rhs) + { + } + + int Nikon3MakerNote::readHeader(const byte* buf, + long len, + ByteOrder byteOrder) + { + if (len < 18) return 1; + + header_.alloc(18); + memcpy(header_.pData_, buf, header_.size_); + TiffHeader tiffHeader; + tiffHeader.read(header_.pData_ + 10); + byteOrder_ = tiffHeader.byteOrder(); + adjOffset_ = tiffHeader.offset(); + return 0; + } + + int Nikon3MakerNote::checkHeader() const + { + int rc = 0; + // Check the Nikon prefix + if ( header_.size_ < 18 + || std::string(reinterpret_cast<char*>(header_.pData_), 6) + != std::string("Nikon\0", 6)) { + rc = 2; + } + return rc; + } + + Nikon3MakerNote::AutoPtr Nikon3MakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + Nikon3MakerNote* Nikon3MakerNote::create_(bool alloc) const + { + AutoPtr makerNote(new Nikon3MakerNote(alloc)); + assert(makerNote.get() != 0); + makerNote->readHeader(header_.pData_, header_.size_, byteOrder_); + return makerNote.release(); + } + + Nikon3MakerNote::AutoPtr Nikon3MakerNote::clone() const + { + return AutoPtr(clone_()); + } + + Nikon3MakerNote* Nikon3MakerNote::clone_() const + { + return new Nikon3MakerNote(*this); + } + + std::ostream& Nikon3MakerNote::print0x0002(std::ostream& os, + const Value& value) + { + if (value.count() > 1) { + os << value.toLong(1); + } + else { + os << "(" << value << ")"; + } + return os; + } + + std::ostream& Nikon3MakerNote::print0x0012(std::ostream& os, + const Value& value) + { + // From the PHP JPEG Metadata Toolkit + long fec = value.toLong(); + switch (fec) { + case 0x06: os << "+1.0 EV"; break; + case 0x04: os << "+0.7 EV"; break; + case 0x03: os << "+0.5 EV"; break; + case 0x02: os << "+0.3 EV"; break; + case 0x00: os << "0.0 EV"; break; + case 0xfe: os << "-0.3 EV"; break; + case 0xfd: os << "-0.5 EV"; break; + case 0xfc: os << "-0.7 EV"; break; + case 0xfa: os << "-1.0 EV"; break; + case 0xf8: os << "-1.3 EV"; break; + case 0xf7: os << "-1.5 EV"; break; + case 0xf6: os << "-1.7 EV"; break; + case 0xf4: os << "-2.0 EV"; break; + case 0xf2: os << "-2.3 EV"; break; + case 0xf1: os << "-2.5 EV"; break; + case 0xf0: os << "-2.7 EV"; break; + case 0xee: os << "-3.0 EV"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& Nikon3MakerNote::print0x0084(std::ostream& os, + const Value& value) + { + if (value.count() == 4) { + long len1 = value.toLong(0); + long len2 = value.toLong(1); + Rational fno1 = value.toRational(2); + Rational fno2 = value.toRational(3); + os << len1; + if (len2 != len1) { + os << "-" << len2; + } + os << "mm " + << "F" << (float)fno1.first / fno1.second; + if (fno2 != fno1) { + os << "-" << (float)fno2.first / fno2.second; + } + } + else { + os << "(" << value << ")"; + } + return os; + } + + std::ostream& Nikon3MakerNote::print0x0087(std::ostream& os, + const Value& value) + { + // From Exiftool + long flash = value.toLong(); + switch (flash) { + case 0: os << "Not used"; break; + case 8: os << "Fired, commander mode"; break; + case 9: os << "Fired, TTL mode"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& Nikon3MakerNote::print0x0088(std::ostream& os, + const Value& value) + { + // Mappings taken from Exiftool + long afpos = value.toLong(); + switch (afpos) { + case 0x0000: os << "Center"; break; + case 0x0100: os << "Top"; break; + case 0x0200: os << "Bottom"; break; + case 0x0300: os << "Left"; break; + case 0x0400: os << "Right"; break; + + // D70 + case 0x00001: os << "Single area, center"; break; + case 0x10002: os << "Single area, top"; break; + case 0x20004: os << "Single area, bottom"; break; + case 0x30008: os << "Single area, left"; break; + case 0x40010: os << "Single area, right"; break; + + case 0x1000001: os << "Dynamic area, center"; break; + case 0x1010002: os << "Dynamic area, top"; break; + case 0x1020004: os << "Dynamic area, bottom"; break; + case 0x1030008: os << "Dynamic area, left"; break; + case 0x1040010: os << "Dynamic area, right"; break; + + case 0x2000001: os << "Closest subject, center"; break; + case 0x2010002: os << "Closest subject, top"; break; + case 0x2020004: os << "Closest subject, bottom"; break; + case 0x2030008: os << "Closest subject, left"; break; + case 0x2040010: os << "Closest subject, right"; break; + + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& Nikon3MakerNote::print0x0089(std::ostream& os, + const Value& value) + { + // From Exiftool + long b = value.toLong(); + switch (b) { + case 0x00: os << "Single"; break; + case 0x01: os << "Continuous"; break; + case 0x02: os << "Delay"; break; + case 0x03: os << "Remote with delay"; break; + case 0x04: os << "Remote"; break; + case 0x16: os << "Exposure bracketing"; break; + case 0x64: os << "White balance bracketing"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& Nikon3MakerNote::print0x008b(std::ostream& os, + const Value& value) + { + // Decoded by Robert Rottmerhusen <email@rottmerhusen.com> + if (value.size() != 4) return os << "(" << value << ")"; + float a = value.toFloat(0); + long b = value.toLong(1); + long c = value.toLong(2); + if (c == 0) return os << "(" << value << ")"; + return os << a * b / c; + } + + std::ostream& Nikon3MakerNote::print0x0098(std::ostream& os, + const Value& value) + { +#ifdef EXV_HAVE_LENSDATA + //# List of AF F-Mount lenses - version 1.12 + //#----------------------------------------- + //# created by Robert Rottmerhusen 2005 + //# for use in non-commercial, GPL or open source software only! + //# please contact me for adding lenses or use in commercial software. + //# + //#"data from TAG 0x98" "ltyp""manuf" "lens name from manuf"; + //# + struct {unsigned char lid,stps,focs,focl,aps,apl,lfw, ltype; char *manuf, *lensname;} + fmountlens[] = { + {0x01,0x58,0x50,0x50,0x14,0x14,0x02,0x00, "Nikon", "AF Nikkor 50mm f/1.8"}, + {0x02,0x42,0x44,0x5C,0x2A,0x34,0x08,0x00, "Nikon", "AF Zoom-Nikkor 35-70mm f/3.3-4.5"}, + {0x04,0x48,0x3C,0x3C,0x24,0x24,0x03,0x00, "Nikon", "AF Nikkor 28mm f/2.8"}, + {0x05,0x54,0x50,0x50,0x0C,0x0C,0x04,0x00, "Nikon", "AF Nikkor 50mm f/1.4"}, + {0x07,0x40,0x3C,0x62,0x2C,0x34,0x03,0x00, "Nikon", "AF Zoom-Nikkor 28-85mm f/3.5-4.5"}, + {0x08,0x40,0x44,0x6A,0x2C,0x34,0x04,0x00, "Nikon", "AF Zoom-Nikkor 35-105mm f/3.5-4.5"}, + {0x09,0x48,0x37,0x37,0x24,0x24,0x04,0x00, "Nikon", "AF Nikkor 24mm f/2.8"}, + {0x0A,0x48,0x8E,0x8E,0x24,0x24,0x03,0x00, "Nikon", "AF Nikkor 300mm f/2.8 IF-ED"}, + {0x0B,0x48,0x7C,0x7C,0x24,0x24,0x05,0x00, "Nikon", "AF Nikkor 180mm f/2.8 IF-ED"}, + {0x0E,0x48,0x5C,0x81,0x30,0x30,0x05,0x00, "Nikon", "AF Zoom-Nikkor 70-210mm f/4"}, + {0x0F,0x58,0x50,0x50,0x14,0x14,0x05,0x00, "Nikon", "AF Nikkor 50mm f/1.8 N"}, + {0x10,0x48,0x8E,0x8E,0x30,0x30,0x08,0x00, "Nikon", "AF Nikkor 300mm f/4 IF-ED"}, + {0x12,0x48,0x5C,0x81,0x30,0x3C,0x09,0x00, "Nikon", "AF Nikkor 70-210mm f/4-5.6"}, + {0x13,0x42,0x37,0x50,0x2A,0x34,0x0B,0x00, "Nikon", "AF Zoom-Nikkor 24-50mm f/3.3-4.5"}, + {0x14,0x48,0x60,0x80,0x24,0x24,0x0B,0x00, "Nikon", "AF Zoom-Nikkor 80-200mm f/2.8 ED"}, + {0x15,0x4C,0x62,0x62,0x14,0x14,0x0C,0x00, "Nikon", "AF Nikkor 85mm f/1.8"}, + {0x1B,0x44,0x5E,0x8E,0x34,0x3C,0x10,0x00, "Nikon", "AF Zoom-Nikkor 75-300mm f/4.5-5.6"}, + {0x1C,0x48,0x30,0x30,0x24,0x24,0x12,0x00, "Nikon", "AF Nikkor 20mm f/2.8"}, + {0x1D,0x42,0x44,0x5C,0x2A,0x34,0x12,0x00, "Nikon", "AF Zoom-Nikkor 35-70mm f/3.3-4.5 N"}, + {0x1E,0x54,0x56,0x56,0x24,0x24,0x13,0x00, "Nikon", "AF Micro-Nikkor 60mm f/2.8"}, + {0x25,0x48,0x44,0x5c,0x24,0x24,0x1B,0x02, "Nikon", "AF Zoom-Nikkor 35-70mm f/2.8D"}, + {0x25,0x48,0x44,0x5c,0x24,0x24,0x52,0x02, "Nikon", "AF Zoom-Nikkor 35-70mm f/2.8D N"}, + {0x27,0x48,0x8E,0x8E,0x24,0x24,0xF2,0x02, "Nikon", "AF-I Nikkor 300mm f/2.8D IF-ED"}, + {0x2A,0x54,0x3C,0x3C,0x0C,0x0C,0x26,0x02, "Nikon", "AF Nikkor 28mm f/1.4D"}, + {0x2C,0x48,0x6A,0x6A,0x18,0x18,0x27,0x02, "Nikon", "AF DC-Nikkor 105mm f/2D"}, + {0x2D,0x48,0x80,0x80,0x30,0x30,0x21,0x02, "Nikon", "AF Micro-Nikkor 200mm f/4D IF-ED"}, + {0x31,0x54,0x56,0x56,0x24,0x24,0x25,0x02, "Nikon", "AF Micro-Nikkor 60mm f/2.8D"}, + {0x32,0x54,0x6A,0x6A,0x24,0x24,0x35,0x02, "Nikon", "AF Micro-Nikkor 105mm f/2.8D"}, + {0x33,0x48,0x2D,0x2D,0x24,0x24,0x31,0x02, "Nikon", "AF Nikkor 18mm f/2.8D"}, + {0x36,0x48,0x37,0x37,0x24,0x24,0x34,0x02, "Nikon", "AF Nikkor 24mm f/2.8D"}, + {0x37,0x48,0x30,0x30,0x24,0x24,0x36,0x02, "Nikon", "AF Nikkor 20mm f/2.8D"}, + {0x38,0x4C,0x62,0x62,0x14,0x14,0x37,0x02, "Nikon", "AF Nikkor 85mm f/1.8D"}, + {0x3B,0x48,0x44,0x5C,0x24,0x24,0x3A,0x02, "Nikon", "AF Zoom-Nikkor 35-70mm f/2.8D N"}, + {0x3E,0x48,0x3C,0x3C,0x24,0x24,0x3D,0x02, "Nikon", "AF Nikkor 28mm f/2.8D"}, + {0x41,0x48,0x7c,0x7c,0x24,0x24,0x43,0x02, "Nikon", "AF Nikkor 180mm f/2.8D IF-ED"}, + {0x42,0x54,0x44,0x44,0x18,0x18,0x44,0x02, "Nikon", "AF Nikkor 35mm f/2D"}, + {0x43,0x54,0x50,0x50,0x0C,0x0C,0x46,0x02, "Nikon", "AF Nikkor 50mm f/1.4D"}, + {0x46,0x3C,0x44,0x60,0x30,0x3C,0x49,0x02, "Nikon", "AF Zoom-Nikkor 35-80mm f/4-5.6D"}, + {0x48,0x48,0x8E,0x8E,0x24,0x24,0x4B,0x02, "Nikon", "AF-S Nikkor 300mm f/2.8D IF-ED"}, + {0x4A,0x54,0x62,0x62,0x0C,0x0C,0x4D,0x02, "Nikon", "AF Nikkor 85mm f/1.4D IF"}, + {0x4C,0x40,0x37,0x6E,0x2C,0x3C,0x4F,0x02, "Nikon", "AF Zoom-Nikkor 24-120mm f/3.5-5.6D IF"}, + {0x4D,0x40,0x3C,0x80,0x2C,0x3C,0x62,0x02, "Nikon", "AF Zoom-Nikkor 28-200mm f/3.5-5.6D IF"}, + {0x4E,0x48,0x72,0x72,0x18,0x18,0x51,0x02, "Nikon", "AF DC-Nikkor 135mm f/2D"}, + {0x53,0x48,0x60,0x80,0x24,0x24,0x60,0x02, "Nikon", "AF Zoom-Nikkor 80-200mm f/2.8D ED"}, + {0x54,0x44,0x5C,0x7C,0x34,0x3C,0x58,0x02, "Nikon", "AF Zoom-Micro Nikkor 70-180mm f/4.5-5.6D ED"}, + {0x56,0x48,0x5C,0x8E,0x30,0x3C,0x5A,0x02, "Nikon", "AF Zoom-Nikkor 70-300mm f/4-5.6D ED"}, + {0x59,0x48,0x98,0x98,0x24,0x24,0x5D,0x02, "Nikon", "AF-S Nikkor 400mm f/2.8D IF-ED"}, + {0x5A,0x3C,0x3E,0x56,0x30,0x3C,0x5E,0x06, "Nikon", "IX-Nikkor 30-60mm f/4-5.6"}, + {0x5D,0x48,0x3C,0x5C,0x24,0x24,0x63,0x02, "Nikon", "AF-S Zoom-Nikkor 28-70mm f/2.8D IF-ED"}, + {0x5E,0x48,0x60,0x80,0x24,0x24,0x64,0x02, "Nikon", "AF-S Zoom-Nikkor 80-200mm f/2.8D IF-ED"}, + {0x5F,0x40,0x3C,0x6A,0x2C,0x34,0x65,0x02, "Nikon", "AF Zoom-Nikkor 28-105mm f/3.5-4.5D IF"}, + {0x63,0x48,0x2B,0x44,0x24,0x24,0x68,0x02, "Nikon", "AF-S Nikkor 17-35mm f/2.8D IF-ED"}, + {0x64,0x00,0x62,0x62,0x24,0x24,0x6A,0x02, "Nikon", "PC Micro-Nikkor 85mm f/2.8D"}, + {0x65,0x44,0x60,0x98,0x34,0x3C,0x6B,0x0A, "Nikon", "AF VR Zoom-Nikkor 80-400mm f/4.5-5.6D ED"}, + {0x66,0x40,0x2D,0x44,0x2C,0x34,0x6C,0x02, "Nikon", "AF Zoom-Nikkor 18-35mm f/3.5-4.5D IF-ED"}, + {0x67,0x48,0x37,0x62,0x24,0x30,0x6D,0x02, "Nikon", "AF Zoom-Nikkor 24-85mm f/2.8-4D IF"}, + {0x68,0x42,0x3C,0x60,0x2A,0x3C,0x6E,0x06, "Nikon", "AF Zoom-Nikkor 28-80mm f/3.3-5.6G"}, + {0x69,0x48,0x5C,0x8E,0x30,0x3C,0x6F,0x06, "Nikon", "AF Zoom-Nikkor 70-300mm f/4-5.6G"}, + {0x6A,0x48,0x8E,0x8E,0x30,0x30,0x70,0x02, "Nikon", "AF-S Nikkor 300mm f/4D IF-ED"}, + {0x6D,0x48,0x8E,0x8E,0x24,0x24,0x73,0x02, "Nikon", "AF-S Nikkor 300mm f/2.8D IF-ED II"}, + {0x6E,0x48,0x98,0x98,0x24,0x24,0x74,0x02, "Nikon", "AF-S Nikkor 400mm f/2.8D IF-ED II"}, + {0x70,0x3C,0xA6,0xA6,0x30,0x30,0x76,0x02, "Nikon", "AF-S Nikkor 600mm f/4D IF-ED"}, + {0x72,0x48,0x4C,0x4C,0x24,0x24,0x77,0x00, "Nikon", "Nikkor 45mm f/2.8 P"}, + {0x74,0x40,0x37,0x62,0x2C,0x34,0x78,0x06, "Nikon", "AF-S Zoom-Nikkor 24-85mm f/3.5-4.5G IF-ED"}, + {0x75,0x40,0x3C,0x68,0x2C,0x3C,0x79,0x06, "Nikon", "AF Zoom-Nikkor 28-100mm f/3.5-5.6G"}, + {0x76,0x58,0x50,0x50,0x14,0x14,0x7A,0x02, "Nikon", "AF Nikkor 50mm f/1.8D"}, + {0x77,0x48,0x5C,0x80,0x24,0x24,0x7B,0x0E, "Nikon", "AF-S VR Zoom-Nikkor 70-200mm f/2.8G IF-ED"}, + {0x78,0x40,0x37,0x6E,0x2C,0x3C,0x7C,0x0E, "Nikon", "AF-S VR Zoom-Nikkor 24-120mm f/3.5-5.6G IF-ED"}, + {0x79,0x40,0x3C,0x80,0x2C,0x3C,0x7F,0x06, "Nikon", "AF Zoom-Nikkor 28-200mm f/3.5-5.6G IF-ED"}, + {0x7A,0x3C,0x1F,0x37,0x30,0x30,0x7E,0x06, "Nikon", "AF-S DX Zoom-Nikkor 12-24mm f/4G IF-ED"}, + {0x7B,0x48,0x80,0x98,0x30,0x30,0x80,0x0E, "Nikon", "AF-S VR Zoom-Nikkor 200-400mm f/4G IF-ED"}, + {0x7D,0x48,0x2B,0x53,0x24,0x24,0x82,0x06, "Nikon", "AF-S DX Zoom-Nikkor 17-55mm f/2.8G IF-ED"}, + {0x7F,0x40,0x2D,0x5C,0x2C,0x34,0x84,0x06, "Nikon", "AF-S DX Zoom-Nikkor 18-70mm f/3.5-4.5G IF-ED"}, + {0x80,0x48,0x1A,0x1A,0x24,0x24,0x85,0x06, "Nikon", "AF DX Fisheye-Nikkor 10.5mm f/2.8G ED"}, + {0x81,0x54,0x80,0x80,0x18,0x18,0x86,0x0E, "Nikon", "AF-S VR Nikkor 200mm f/2G IF-ED"}, + {0x82,0x48,0x8E,0x8E,0x24,0x24,0x87,0x0E, "Nikon", "AF-S VR Nikkor 300mm f/2.8G IF-ED"}, + {0x89,0x3C,0x53,0x80,0x30,0x3C,0x8B,0x06, "Nikon", "AF-S DX Zoom-Nikkor 55-200mm f/4-5.6G ED"}, + {0x8C,0x40,0x2D,0x53,0x2C,0x3C,0x8E,0x06, "Nikon", "AF-S DX Zoom-Nikkor 18-55mm f/3.5-5.6G ED"}, + {0x06,0x3F,0x68,0x68,0x2C,0x2C,0x06,0x00, "Cosina", "100mm F/3.5 Macro"}, + {0x02,0x3F,0x24,0x24,0x2C,0x2C,0x02,0x00, "Sigma", "14mm F3.5"}, + {0x02,0x46,0x37,0x37,0x25,0x25,0x02,0x00, "Sigma", "24mm F2.8 Macro"}, + {0x02,0x3F,0x3C,0x5C,0x2D,0x35,0x02,0x00, "Sigma", "28-70mm F3.5-4.5 UC"}, + {0x02,0x40,0x44,0x73,0x2B,0x36,0x02,0x00, "Sigma", "35-135mm F3.5-4.5 a"}, + {0x02,0x37,0x5E,0x8E,0x35,0x3D,0x02,0x00, "Sigma", "75-300mm F4.5-5.6 APO"}, + {0x02,0x48,0x65,0x65,0x24,0x24,0x02,0x00, "Sigma", "90mm F2.8 Macro"}, + {0x02,0x2F,0x98,0x98,0x3D,0x3D,0x02,0x00, "Sigma", "400mm F5.6 APO"}, + {0x26,0x40,0x3C,0x8E,0x2C,0x40,0x1C,0x02, "Sigma", "28-300mm F3.5-6.3 Macro D"}, + {0x26,0x40,0x3C,0x80,0x2B,0x3C,0x1C,0x02, "Sigma", "28-200mm F3.5-5.6 Compact Aspherical Hyperzoom Macro D"}, + {0x26,0x40,0x3C,0x60,0x2C,0x3C,0x1C,0x02, "Sigma", "28-80mm F3.5-5.6 Mini Zoom Macro II Aspherical D"}, + {0x26,0x54,0x37,0x5C,0x24,0x24,0x1C,0x02, "Sigma", "24-70mm F2.8 EX DG Macro D"}, + {0x26,0x40,0x2D,0x70,0x2B,0x3C,0x1C,0x06, "Sigma", "18-125mm F3.5-5.6 DC G"}, + {0x26,0x48,0x2D,0x50,0x24,0x24,0x1C,0x06, "Sigma", "18-50mm F2.8 EX DC G"}, + {0x26,0x40,0x2D,0x50,0x2C,0x3C,0x1C,0x06, "Sigma", "18-50mm F3.5-5.6 DC G"}, + {0x48,0x38,0x1F,0x37,0x34,0x3C,0x4B,0x06, "Sigma", "12-24mm F4.5-5.6 EX Aspherical DG HSM G"}, + {0x48,0x48,0x2B,0x44,0x24,0x30,0x4B,0x06, "Sigma", "17-35mm F2.8-4 EX DG Aspherical HSM G"}, + {0x48,0x3C,0x50,0xA0,0x30,0x40,0x4B,0x02, "Sigma", "50-500mmF4-6.3 EX APO RF HSM D"}, + {0x48,0x54,0x5C,0x80,0x24,0x24,0x4B,0x02, "Sigma", "70-200mm F2.8 EX APO IF HSM D"}, + {0x48,0x48,0x68,0x8E,0x30,0x30,0x4B,0x02, "Sigma", "100-300mm F4 EX IF HSM D"}, + {0x48,0x48,0x76,0x76,0x24,0x24,0x4B,0x06, "Sigma", "150mm F2.8 EX DG APO Macro HSM G"}, + {0x77,0x44,0x61,0x98,0x34,0x3C,0x7B,0x0E, "Sigma", "80-400mm f4.5-5.6 EX OS G"}, + {0x03,0x43,0x5C,0x81,0x35,0x35,0x02,0x00, "Soligor", "AF C/D ZOOM UMCS 70-210mm 1:4.5"}, + {0x00,0x3C,0x1F,0x37,0x30,0x30,0x00,0x06, "Tokina", "AT-X 124 AF PRO DX - AF 12-24mm f/4"}, + {0x00,0x40,0x2B,0x2B,0x2C,0x2C,0x00,0x02, "Tokina", "AT-X 17 AF PRO - AF 17mm f/3.5"}, + {0x00,0x54,0x68,0x68,0x24,0x24,0x00,0x02, "Tokina", "AT-X M100 PRO D - 100mm F2.8"}, + {0x4D,0x41,0x3C,0x8E,0x2B,0x40,0x62,0x02, "Tamron", "AF28-300mm F/3.5-6.3 XR Di LD Aspherical (IF)"}, + {0x00,0x3F,0x2D,0x80,0x2B,0x40,0x00,0x06, "Tamron", "AF18-200mm F/3.5-6.3 XR Di II LD Aspherical (IF)"}, + {0x32,0x53,0x64,0x64,0x24,0x24,0x35,0x02, "Tamron", "SP AF90mm F/2.8 Di 1:1 Macro"}, + {0x00,0x48,0x3C,0x6A,0x24,0x24,0x00,0x02, "Unknown", "28-105mm F/2.8D"}, + {0x00,0x49,0x30,0x48,0x22,0x2B,0x00,0x02, "Unknown", "20-40mm F/2.7-3.3D"}, + {0x07,0x46,0x2B,0x44,0x24,0x30,0x03,0x02, "Unknown", "AF17-35mm D"}, + {0x1E,0x5D,0x64,0x64,0x20,0x20,0x13,0x00, "Unknown", "90mm F/2.5"}, + {0x20,0x3C,0x80,0x98,0x3D,0x3D,0x1E,0x02, "Unknown", "200-400mm F/5.6D"}, + {0x2F,0x40,0x30,0x44,0x2C,0x34,0x29,0x02, "Unknown", "20-35mm F/3.5-4.5D"}, + {0,0,0,0,0,0,0,0, NULL, NULL} + }; + + if (value.typeId() != undefined) return os << value; + + DataBuf lens(value.size()); + // ByteOrder is only to satisfy the interface + value.copy(lens.pData_, invalidByteOrder); + + int idx = 0; + if (0 == memcmp(lens.pData_, "0100", 4)) { + idx = 6; + } + else if (0 == memcmp(lens.pData_, "0101", 4)) { + idx = 11; + } + else if (0 == memcmp(lens.pData_, "0201", 4)) { + // Here we should decrypt(lens.pData_ + 4, lens.size_ - 4); + // however, the decrypt algorithm requires access to serial number + // and shutter count tags but print functions are static... + idx = 11; + } + if (idx == 0 || lens.size_ < idx + 7) { + // Unknown version or not enough data + return os << value; + } + for (int i = 0; fmountlens[i].lensname != NULL; ++i) { + if ( lens.pData_[idx] == fmountlens[i].lid + && lens.pData_[idx+1] == fmountlens[i].stps + && lens.pData_[idx+2] == fmountlens[i].focs + && lens.pData_[idx+3] == fmountlens[i].focl + && lens.pData_[idx+4] == fmountlens[i].aps + && lens.pData_[idx+5] == fmountlens[i].apl + && lens.pData_[idx+6] == fmountlens[i].lfw) { + // Lens found in database + return os << fmountlens[i].manuf << " " << fmountlens[i].lensname; + } + } + // Lens not found in database + return os << value; +#else + return os << value; +#endif // EXV_HAVE_LENSDATA + } + +// ***************************************************************************** +// free functions + + MakerNote::AutoPtr createNikonMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset) + { + // If there is no "Nikon" string it must be Nikon1 format + if (len < 6 || std::string(reinterpret_cast<const char*>(buf), 6) + != std::string("Nikon\0", 6)) { + return MakerNote::AutoPtr(new Nikon1MakerNote(alloc)); + } + // If the "Nikon" string is not followed by a TIFF header, we assume + // Nikon2 format + TiffHeader tiffHeader; + if ( len < 18 + || tiffHeader.read(buf + 10) != 0 || tiffHeader.tag() != 0x002a) { + return MakerNote::AutoPtr(new Nikon2MakerNote(alloc)); + } + // Else we have a Nikon3 makernote + return MakerNote::AutoPtr(new Nikon3MakerNote(alloc)); + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/nikonmn.hpp b/src/plugins/exiv2/nikonmn.hpp @@ -0,0 +1,309 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file nikonmn.hpp + @brief Nikon MakerNote formats. + + The Nikon MakerNote formats are implemented according to the following references<BR> + Format 1: + <ul> + <li><a href="http://www.tawbaware.com/990exif.htm">MakerNote EXIF Tag of the Nikon 990</a> by Max Lyons</li></ul> + Format 2: + <ul><li>"Appendix 2: Makernote of Nikon" of the document + <a href="http://park2.wakwak.com/%7Etsuruzoh/Computer/Digicams/exif-e.html"> + Exif file format</a> by TsuruZoh Tachibanaya</li></ul> + Format 3: + <ul><li>"EXIFutils Field Reference Guide"</li> + <li><a href="http://www.ozhiker.com/electronics/pjmt/jpeg_info/nikon_mn.html#Nikon_Type_3_Tags">Nikon Type 3 Makernote Tags Definition</a> + of the PHP JPEG Metadata Toolkit by Evan Hunter</li> + <li>Nikon tag information from <a href="http://www.sno.phy.queensu.ca/~phil/exiftool/">ExifTool</a> by Phil Harvey</li> + <li>Email communication with Robert Rottmerhusen</li> + </ul> + + @version $Rev: 588 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 17-May-04, ahu: created<BR> + 25-May-04, ahu: combined all Nikon formats in one component + */ +#ifndef NIKONMN_HPP_ +#define NIKONMN_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "makernote.hpp" +#include "tags.hpp" + +// + standard includes +#include <string> +#include <iosfwd> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class Value; + +// ***************************************************************************** +// free functions + + /*! + @brief Return an auto-pointer to a newly created empty MakerNote + initialized to operate in the memory management model indicated. + The caller owns this copy and the auto-pointer ensures that it + will be deleted. + + @param alloc Memory management model for the new MakerNote. Determines if + memory required to store data should be allocated and deallocated + (true) or not (false). If false, only pointers to the buffer + provided to read() will be kept. See Ifd for more background on + this concept. + @param buf Pointer to the makernote character buffer (not used). + @param len Length of the makernote character buffer (not used). + @param byteOrder Byte order in which the Exif data (and possibly the + makernote) is encoded (not used). + @param offset Offset from the start of the TIFF header of the makernote + buffer (not used). + + @return An auto-pointer to a newly created empty MakerNote. The caller + owns this copy and the auto-pointer ensures that it will be + deleted. + */ + MakerNote::AutoPtr createNikonMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset); + +// ***************************************************************************** +// class definitions + + //! A MakerNote format used by Nikon cameras, such as the E990 and D1. + class Nikon1MakerNote : public IfdMakerNote { + public: + //! Shortcut for a %Nikon1MakerNote auto pointer. + typedef std::auto_ptr<Nikon1MakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to choose whether or not memory management + is required for the makernote entries. + */ + Nikon1MakerNote(bool alloc =true); + //! Copy constructor + Nikon1MakerNote(const Nikon1MakerNote& rhs); + //! Virtual destructor + virtual ~Nikon1MakerNote() {} + //@} + + //! @name Accessors + //@{ + AutoPtr create(bool alloc =true) const; + AutoPtr clone() const; + //@} + + //! @name Print functions for Nikon1 %MakerNote tags + //@{ + //! Print ISO setting + static std::ostream& print0x0002(std::ostream& os, const Value& value); + //! Print autofocus mode + static std::ostream& print0x0007(std::ostream& os, const Value& value); + //! Print manual focus distance + static std::ostream& print0x0085(std::ostream& os, const Value& value); + //! Print digital zoom setting + static std::ostream& print0x0086(std::ostream& os, const Value& value); + //! Print AF focus position + static std::ostream& print0x0088(std::ostream& os, const Value& value); + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct RegisterMn { + RegisterMn(); + }; + //! @endcond + + private: + //! Internal virtual create function. + Nikon1MakerNote* create_(bool alloc =true) const; + //! Internal virtual copy constructor. + Nikon1MakerNote* clone_() const; + + //! Tag information + static const TagInfo tagInfo_[]; + + }; // class Nikon1MakerNote + + static Nikon1MakerNote::RegisterMn registerNikon1MakerNote; + + /*! + @brief A second MakerNote format used by Nikon cameras, including the + E700, E800, E900, E900S, E910, E950 + */ + class Nikon2MakerNote : public IfdMakerNote { + public: + //! Shortcut for a %Nikon2MakerNote auto pointer. + typedef std::auto_ptr<Nikon2MakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to choose whether or not memory management + is required for the makernote entries. + */ + Nikon2MakerNote(bool alloc =true); + //! Copy constructor + Nikon2MakerNote(const Nikon2MakerNote& rhs); + //! Virtual destructor + virtual ~Nikon2MakerNote() {} + //@} + + //! @name Manipulators + //@{ + int readHeader(const byte* buf, + long len, + ByteOrder byteOrder); + //@} + + //! @name Accessors + //@{ + int checkHeader() const; + AutoPtr create(bool alloc =true) const; + AutoPtr clone() const; + //@} + + //! @name Print functions for Nikon2 %MakerNote tags + //@{ + //! Print quality setting + static std::ostream& print0x0003(std::ostream& os, const Value& value); + //! Print color mode setting + static std::ostream& print0x0004(std::ostream& os, const Value& value); + //! Print image adjustment setting + static std::ostream& print0x0005(std::ostream& os, const Value& value); + //! Print ISO speed setting + static std::ostream& print0x0006(std::ostream& os, const Value& value); + //! Print white balance setting + static std::ostream& print0x0007(std::ostream& os, const Value& value); + //! Print digital zoom setting + static std::ostream& print0x000a(std::ostream& os, const Value& value); + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct RegisterMn { + RegisterMn(); + }; + //! @endcond + + private: + //! Internal virtual create function. + Nikon2MakerNote* create_(bool alloc =true) const; + //! Internal virtual copy constructor. + Nikon2MakerNote* clone_() const; + + //! Tag information + static const TagInfo tagInfo_[]; + + }; // class Nikon2MakerNote + + static Nikon2MakerNote::RegisterMn registerNikon2MakerNote; + + //! A third MakerNote format used by Nikon cameras, e.g., E5400, SQ, D2H, D70 + class Nikon3MakerNote : public IfdMakerNote { + public: + //! Shortcut for a %Nikon3MakerNote auto pointer. + typedef std::auto_ptr<Nikon3MakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to choose whether or not memory management + is required for the makernote entries. + */ + Nikon3MakerNote(bool alloc =true); + //! Copy constructor + Nikon3MakerNote(const Nikon3MakerNote& rhs); + //! Virtual destructor + virtual ~Nikon3MakerNote() {} + //@} + + //! @name Manipulators + //@{ + int readHeader(const byte* buf, + long len, + ByteOrder byteOrder); + //@} + + //! @name Accessors + //@{ + int checkHeader() const; + AutoPtr create(bool alloc =true) const; + AutoPtr clone() const; + //@} + + //! @name Print functions for Nikon3 %MakerNote tags + //@{ + //! Print ISO setting + static std::ostream& print0x0002(std::ostream& os, const Value& value); + //! Print flash compensation + static std::ostream& print0x0012(std::ostream& os, const Value& value); + //! Print lens information + static std::ostream& print0x0084(std::ostream& os, const Value& value); + //! Print flash used information + static std::ostream& print0x0087(std::ostream& os, const Value& value); + //! Print AF point + static std::ostream& print0x0088(std::ostream& os, const Value& value); + //! Print bracketing information + static std::ostream& print0x0089(std::ostream& os, const Value& value); + //! Print number of lens stops + static std::ostream& print0x008b(std::ostream& os, const Value& value); + //! Print number of lens data + static std::ostream& print0x0098(std::ostream& os, const Value& value); + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct RegisterMn { + RegisterMn(); + }; + //! @endcond + + private: + //! Internal virtual create function. + Nikon3MakerNote* create_(bool alloc =true) const; + //! Internal virtual copy constructor. + Nikon3MakerNote* clone_() const; + + //! Tag information + static const TagInfo tagInfo_[]; + + }; // class Nikon3MakerNote + + static Nikon3MakerNote::RegisterMn registerNikon3MakerNote; + +} // namespace Exiv2 + +#endif // #ifndef NIKONMN_HPP_ diff --git a/src/plugins/exiv2/olympusmn.cpp b/src/plugins/exiv2/olympusmn.cpp @@ -0,0 +1,318 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: olympusmn.cpp + Version: $Rev: 581 $ + Author(s): Will Stokes (wuz) <wstokes@gmail.com> + Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 10-Mar-05, wuz: created + Credits: See header file. + */ + +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: olympusmn.cpp 581 2005-06-12 05:54:57Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "olympusmn.hpp" +#include "makernote.hpp" +#include "value.hpp" + +// + standard includes +#include <string> +#include <sstream> +#include <iomanip> +#include <cassert> + +// Define DEBUG_MAKERNOTE to output debug information to std::cerr +#undef DEBUG_MAKERNOTE + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + //! @cond IGNORE + OlympusMakerNote::RegisterMn::RegisterMn() + { + MakerNoteFactory::registerMakerNote( + "OLYMPUS*", "*", createOlympusMakerNote); + MakerNoteFactory::registerMakerNote( + olympusIfdId, MakerNote::AutoPtr(new OlympusMakerNote)); + + ExifTags::registerMakerTagInfo(olympusIfdId, tagInfo_); + } + //! @endcond + + // Olympus Tag Info + const TagInfo OlympusMakerNote::tagInfo_[] = { + TagInfo(0x0200, "SpecialMode", "Picture taking mode", olympusIfdId, makerTags, unsignedLong, print0x0200), + TagInfo(0x0201, "Quality", "Image quality setting", olympusIfdId, makerTags, unsignedShort, print0x0201), + TagInfo(0x0202, "Macro", "Macro mode", olympusIfdId, makerTags, unsignedShort, print0x0202), + TagInfo(0x0203, "BWMode", "Black and White Mode", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0204, "DigitalZoom", "Digital zoom ratio", olympusIfdId, makerTags, unsignedRational, print0x0204), + TagInfo(0x0205, "FocalPlaneDiagonal", "Focal plane diagonal", olympusIfdId, makerTags, unsignedRational, printValue), + TagInfo(0x0206, "0x0206", "Unknown", olympusIfdId, makerTags, signedShort, printValue), + TagInfo(0x0207, "FirmwareVersion", "Software firmware version", olympusIfdId, makerTags, asciiString, printValue), + TagInfo(0x0208, "PictureInfo", "ASCII format data such as [PictureInfo]", olympusIfdId, makerTags, asciiString, printValue), + TagInfo(0x0209, "CameraID", "CameraID data", olympusIfdId, makerTags, undefined, printValue), + TagInfo(0x0300, "PreCaptureFrames", "Pre-capture frames", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0301, "0x0301", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0302, "OneTouchWB", "OneTouchWB", olympusIfdId, makerTags, unsignedShort, print0x0302), + TagInfo(0x0303, "0x0303", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0304, "0x0304", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0f00, "DataDump", "Various camera settings", olympusIfdId, makerTags, undefined, printValue), + TagInfo(0x1000, "0x1000", "Unknown", olympusIfdId, makerTags, signedRational, printValue), + TagInfo(0x1001, "0x1001", "Unknown", olympusIfdId, makerTags, signedRational, printValue), + TagInfo(0x1002, "0x1002", "Unknown", olympusIfdId, makerTags, signedRational, printValue), + TagInfo(0x1003, "0x1003", "Unknown", olympusIfdId, makerTags, signedRational, printValue), + TagInfo(0x1004, "FlashMode", "Flash mode", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1005, "FlashDevice", "Flash device", olympusIfdId, makerTags, unsignedShort, print0x1005), + TagInfo(0x1006, "Bracket", "Bracket", olympusIfdId, makerTags, signedRational, printValue), + TagInfo(0x1007, "0x1007", "Unknown", olympusIfdId, makerTags, signedShort, printValue), + TagInfo(0x1008, "0x1008", "Unknown", olympusIfdId, makerTags, signedShort, printValue), + TagInfo(0x1009, "0x1009", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x100a, "0x100a", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x100b, "FocusMode", "Focus mode", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x100c, "FocusDistance", "Focus distance", olympusIfdId, makerTags, unsignedRational, printValue), + TagInfo(0x100d, "Zoom", "Zoom", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x100e, "MacroFocus", "Macro focus", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x100f, "SharpnessFactor", "Sharpness factor", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1010, "0x1010", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1011, "ColorMatrix", "Color matrix", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1012, "BlackLevel", "Black level", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1013, "0x1013", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1014, "0x1014", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1015, "WhiteBalance", "White balance", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1016, "0x1016", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1017, "RedBalance", "Red balance", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1018, "BlueBalance", "Blue balance", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1019, "0x1019", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x101a, "SerialNumber", "Serial number", olympusIfdId, makerTags, asciiString, printValue), + TagInfo(0x101b, "0x101b", "Unknown", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x101c, "0x101c", "Unknown", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x101d, "0x101d", "Unknown", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x101e, "0x101e", "Unknown", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x101f, "0x101f", "Unknown", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x1020, "0x1020", "Unknown", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x1021, "0x1021", "Unknown", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x1022, "0x1022", "Unknown", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x1023, "FlashBias", "Flash bias", olympusIfdId, makerTags, signedRational, printValue), + TagInfo(0x1024, "0x1024", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1025, "0x1025", "Unknown", olympusIfdId, makerTags, signedRational, printValue), + TagInfo(0x1026, "0x1026", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1027, "0x1027", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1028, "0x1028", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1029, "Contrast", "Contrast setting", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x102a, "SharpnessFactor", "Sharpness factor", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x102b, "ColorControl", "Color control", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x102c, "ValidBits", "Valid bits", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x102d, "Coring Filter", "Coring filter", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x102e, "ImageWidth", "Image width", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x102f, "ImageHeight", "Image height", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x1030, "0x1030", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1031, "0x1031", "Unknown", olympusIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x1032, "0x1032", "Unknown", olympusIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x1033, "0x1033", "Unknown", olympusIfdId, makerTags, unsignedLong, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownOlympusMakerNoteTag)", "Unknown OlympusMakerNote tag", olympusIfdId, makerTags, invalidTypeId, printValue) + }; + + OlympusMakerNote::OlympusMakerNote(bool alloc) + : IfdMakerNote(olympusIfdId, alloc) + { + byte buf[] = { + 'O', 'L', 'Y', 'M', 'P', 0x00, 0x01, 0x00 + }; + readHeader(buf, 8, byteOrder_); + } + + OlympusMakerNote::OlympusMakerNote(const OlympusMakerNote& rhs) + : IfdMakerNote(rhs) + { + } + + int OlympusMakerNote::readHeader(const byte* buf, + long len, + ByteOrder byteOrder) + { + if (len < 8) return 1; + + // Copy the header + header_.alloc(8); + memcpy(header_.pData_, buf, header_.size_); + // Adjust the offset of the IFD for the prefix + adjOffset_ = 8; + + return 0; + } + + int OlympusMakerNote::checkHeader() const + { + int rc = 0; + // Check the OLYMPUS prefix + if ( header_.size_ < 8 + || std::string(reinterpret_cast<char*>(header_.pData_), 5) + != std::string("OLYMP", 5)) { + rc = 2; + } + return rc; + } + + OlympusMakerNote::AutoPtr OlympusMakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + OlympusMakerNote* OlympusMakerNote::create_(bool alloc) const + { + AutoPtr makerNote = AutoPtr(new OlympusMakerNote(alloc)); + assert(makerNote.get() != 0); + makerNote->readHeader(header_.pData_, header_.size_, byteOrder_); + return makerNote.release(); + } + + OlympusMakerNote::AutoPtr OlympusMakerNote::clone() const + { + return AutoPtr(clone_()); + } + + OlympusMakerNote* OlympusMakerNote::clone_() const + { + return new OlympusMakerNote(*this); + } + + std::ostream& OlympusMakerNote::print0x0200(std::ostream& os, + const Value& value) + { + if (value.count() != 3 || value.typeId() != unsignedLong) { + return os << value; + } + long l0 = value.toLong(0); + switch (l0) { + case 0: os << "Normal"; break; + case 2: os << "Fast"; break; + case 3: os << "Panorama"; break; + default: os << "(" << l0 << ")"; break; + } + if (l0 != 0) { + os << ", "; + long l1 = value.toLong(1); + os << "Sequence number " << l1; + } + if (l0 != 0 && l0 != 2) { + os << ", "; + long l2 = value.toLong(2); + switch (l2) { + case 1: os << "Left to Right"; break; + case 2: os << "Right to Left"; break; + case 3: os << "Bottom to Top"; break; + case 4: os << "Top to Bottom"; break; + default: os << "(" << l2 << ")"; break; + } + } + return os; + } // OlympusMakerNote::print0x0200 + + //! Quality + const TagDetails quality[] = { + { 0, "(start)" }, + { 1, "Standard Quality (SQ)" }, + { 2, "High Quality (HQ)" }, + { 3, "Super High Quality (SHQ)" }, + { 6, "Raw" }, + { 0, "(end)" } + }; + + std::ostream& OlympusMakerNote::print0x0201(std::ostream& os, + const Value& value) + { + return TagTranslator(quality).print(os, value); + } // OlympusMakerNote::print0x0201 + + //! Macro + const TagDetails macro[] = { + { -1, "(start)" }, + { 0, "Off" }, + { 1, "On" }, + { 2, "Super Macro" }, + { -1, "(end)" } + }; + + std::ostream& OlympusMakerNote::print0x0202(std::ostream& os, + const Value& value) + { + return TagTranslator(macro).print(os, value); + } // OlympusMakerNote::print0x0202 + + std::ostream& OlympusMakerNote::print0x0204(std::ostream& os, + const Value& value) + { + float f = value.toFloat(); + if (f == 0.0 || f == 1.0) return os << "None"; + return os << std::fixed << std::setprecision(1) << f << "x"; + } // OlympusMakerNote::print0x0204 + + //! OneTouchWB + const TagDetails oneTouchWb[] = { + { -1, "(start)" }, + { 0, "Off" }, + { 1, "On" }, + { 2, "On (Preset)" }, + { -1, "(end)" } + }; + + std::ostream& OlympusMakerNote::print0x0302(std::ostream& os, + const Value& value) + { + return TagTranslator(oneTouchWb).print(os, value); + } // OlympusMakerNote::print0x0302 + + //! FlashDevice + const TagDetails flashDevice[] = { + { -1, "(start)" }, + { 0, "None" }, + { 1, "Internal" }, + { 4, "External" }, + { 4, "Internal + External" }, + { -1, "(end)" } + }; + + std::ostream& OlympusMakerNote::print0x1005(std::ostream& os, + const Value& value) + { + return TagTranslator(flashDevice).print(os, value); + } // OlympusMakerNote::print0x1005 + +// ***************************************************************************** +// free functions + + MakerNote::AutoPtr createOlympusMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset) + { + return MakerNote::AutoPtr(new OlympusMakerNote(alloc)); + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/olympusmn.hpp b/src/plugins/exiv2/olympusmn.hpp @@ -0,0 +1,161 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file olympusmn.hpp + @brief Olympus MakerNote implemented using the following references: + <a href="http://park2.wakwak.com/%7Etsuruzoh/Computer/Digicams/exif-e.html#APP1">Exif file format, Appendix 1: MakerNote of Olympus Digicams</a> by TsuruZoh Tachibanaya, + Olympus.pm of <a href="http://www.sno.phy.queensu.ca/~phil/exiftool/">ExifTool</a> by Phil Harvey, + <a href="http://www.ozhiker.com/electronics/pjmt/jpeg_info/olympus_mn.html">Olympus Makernote Format Specification</a> by Evan Hunter, + email communication with <a href="mailto:wstokes@gmail.com">Will Stokes</a> + @version $Rev: 580 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @author Will Stokes (wuz) + <a href="mailto:wstokes@gmail.com">wstokes@gmail.com</a> + @date 10-Mar-05, wuz: created + */ +#ifndef OLYMPUSMN_HPP_ +#define OLYMPUSMN_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "makernote.hpp" +#include "tags.hpp" + +// + standard includes +#include <string> +#include <iosfwd> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class Value; + +// ***************************************************************************** +// free functions + + /*! + @brief Return an auto-pointer to a newly created empty MakerNote + initialized to operate in the memory management model indicated. + The caller owns this copy and the auto-pointer ensures that it + will be deleted. + + @param alloc Memory management model for the new MakerNote. Determines if + memory required to store data should be allocated and deallocated + (true) or not (false). If false, only pointers to the buffer + provided to read() will be kept. See Ifd for more background on + this concept. + @param buf Pointer to the makernote character buffer (not used). + @param len Length of the makernote character buffer (not used). + @param byteOrder Byte order in which the Exif data (and possibly the + makernote) is encoded (not used). + @param offset Offset from the start of the TIFF header of the makernote + buffer (not used). + + @return An auto-pointer to a newly created empty MakerNote. The caller + owns this copy and the auto-pointer ensures that it will be + deleted. + */ + MakerNote::AutoPtr createOlympusMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset); + +// ***************************************************************************** +// class definitions + + //! MakerNote for Olympus cameras + class OlympusMakerNote : public IfdMakerNote { + public: + //! Shortcut for a %OlympusMakerNote auto pointer. + typedef std::auto_ptr<OlympusMakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to choose whether or not memory management + is required for the makernote entries. + */ + OlympusMakerNote(bool alloc =true); + //! Copy constructor + OlympusMakerNote(const OlympusMakerNote& rhs); + //! Virtual destructor + virtual ~OlympusMakerNote() {} + //@} + + //! @name Manipulators + //@{ + int readHeader(const byte* buf, + long len, + ByteOrder byteOrder); + //@} + + //! @name Accessors + //@{ + int checkHeader() const; + AutoPtr create(bool alloc =true) const; + AutoPtr clone() const; + //@} + + //! @name Print functions for Olympus %MakerNote tags + //@{ + //! Print 'Special Mode' + static std::ostream& print0x0200(std::ostream& os, const Value& value); + //! Print Jpeg quality + static std::ostream& print0x0201(std::ostream& os, const Value& value); + //! Print Macro mode + static std::ostream& print0x0202(std::ostream& os, const Value& value); + //! Print Digital Zoom Factor + static std::ostream& print0x0204(std::ostream& os, const Value& value); + //! Print OneTouchWB + static std::ostream& print0x0302(std::ostream& os, const Value& value); + //! Print FlashDevice + static std::ostream& print0x1005(std::ostream& os, const Value& value); + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct RegisterMn { + RegisterMn(); + }; + //! @endcond + + private: + //! Internal virtual create function. + OlympusMakerNote* create_(bool alloc =true) const; + //! Internal virtual copy constructor. + OlympusMakerNote* clone_() const; + + //! Tag information + static const TagInfo tagInfo_[]; + + }; // class OlympusMakerNote + + static OlympusMakerNote::RegisterMn registerOlympusMakerNote; +} // namespace Exiv2 + +#endif // #ifndef OLYMPUSMN_HPP_ diff --git a/src/plugins/exiv2/panasonicmn.cpp b/src/plugins/exiv2/panasonicmn.cpp @@ -0,0 +1,361 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: panasonicmn.cpp + Version: $Rev: 581 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 11-Jun-04, ahu: created + Credits: See header file + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: panasonicmn.cpp 581 2005-06-12 05:54:57Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "panasonicmn.hpp" +#include "makernote.hpp" +#include "value.hpp" + +// + standard includes +#include <string> +#include <sstream> +#include <iomanip> +#include <cassert> + +// Define DEBUG_MAKERNOTE to output debug information to std::cerr +#undef DEBUG_MAKERNOTE + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + //! @cond IGNORE + PanasonicMakerNote::RegisterMn::RegisterMn() + { + MakerNoteFactory::registerMakerNote("Panasonic", "*", createPanasonicMakerNote); + MakerNoteFactory::registerMakerNote( + panasonicIfdId, MakerNote::AutoPtr(new PanasonicMakerNote)); + + ExifTags::registerMakerTagInfo(panasonicIfdId, tagInfo_); + } + //! @endcond + + // Panasonic MakerNote Tag Info + const TagInfo PanasonicMakerNote::tagInfo_[] = { + TagInfo(0x0001, "Quality", "Image Quality", panasonicIfdId, makerTags, unsignedShort, print0x0001), + TagInfo(0x0002, "FirmwareVersion", "Firmware version", panasonicIfdId, makerTags, undefined, printValue), + TagInfo(0x0003, "WhiteBalance", "White balance setting", panasonicIfdId, makerTags, unsignedShort, print0x0003), + TagInfo(0x0004, "0x0004", "Unknown", panasonicIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0007, "FocusMode", "Focus mode", panasonicIfdId, makerTags, unsignedShort, print0x0007), + TagInfo(0x000f, "SpotMode", "Spot mode", panasonicIfdId, makerTags, unsignedByte, print0x000f), + TagInfo(0x001a, "ImageStabilizer", "Image stabilizer", panasonicIfdId, makerTags, unsignedShort, print0x001a), + TagInfo(0x001c, "Macro", "Macro mode", panasonicIfdId, makerTags, unsignedShort, print0x001c), + TagInfo(0x001f, "ShootingMode", "Shooting mode", panasonicIfdId, makerTags, unsignedShort, print0x001f), + TagInfo(0x0020, "Audio", "Audio", panasonicIfdId, makerTags, unsignedShort, print0x0020), + TagInfo(0x0021, "DataDump", "Data dump", panasonicIfdId, makerTags, undefined, printValue), + TagInfo(0x0022, "0x0022", "Unknown", panasonicIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0023, "WhiteBalanceBias", "White balance adjustment", panasonicIfdId, makerTags, unsignedShort, print0x0023), + TagInfo(0x0024, "FlashBias", "Flash bias", panasonicIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0025, "SerialNumber", "Serial number", panasonicIfdId, makerTags, undefined, printValue), + TagInfo(0x0026, "0x0026", "Unknown", panasonicIfdId, makerTags, undefined, printValue), + TagInfo(0x0027, "0x0027", "Unknown", panasonicIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0028, "ColorEffect", "Color effect", panasonicIfdId, makerTags, unsignedShort, print0x0028), + TagInfo(0x0029, "0x0029", "Unknown", panasonicIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x002a, "0x002a", "Unknown", panasonicIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x002b, "0x002b", "Unknown", panasonicIfdId, makerTags, unsignedLong, printValue), + TagInfo(0x002c, "Contrast", "Contrast setting", panasonicIfdId, makerTags, unsignedShort, print0x002c), + TagInfo(0x002d, "NoiseReduction", "Noise reduction", panasonicIfdId, makerTags, unsignedShort, print0x002d), + TagInfo(0x002e, "0x002e", "Unknown", panasonicIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x002f, "0x002f", "Unknown", panasonicIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0030, "0x0030", "Unknown", panasonicIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0031, "0x0031", "Unknown", panasonicIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x0032, "0x0032", "Unknown", panasonicIfdId, makerTags, unsignedShort, printValue), + TagInfo(0x4449, "0x4449", "Unknown", panasonicIfdId, makerTags, undefined, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownPanasonicMakerNoteTag)", "Unknown PanasonicMakerNote tag", panasonicIfdId, makerTags, invalidTypeId, printValue) + }; + + PanasonicMakerNote::PanasonicMakerNote(bool alloc) + : IfdMakerNote(panasonicIfdId, alloc, false) + { + byte buf[] = { + 'P', 'a', 'n', 'a', 's', 'o', 'n', 'i', 'c', 0x00, 0x00, 0x00 + }; + readHeader(buf, 12, byteOrder_); + } + + PanasonicMakerNote::PanasonicMakerNote(const PanasonicMakerNote& rhs) + : IfdMakerNote(rhs) + { + } + + int PanasonicMakerNote::readHeader(const byte* buf, + long len, + ByteOrder byteOrder) + { + if (len < 12) return 1; + + header_.alloc(12); + memcpy(header_.pData_, buf, header_.size_); + // Adjust the offset of the IFD for the prefix + adjOffset_ = 12; + return 0; + } + + int PanasonicMakerNote::checkHeader() const + { + int rc = 0; + // Check the Panasonic prefix + if ( header_.size_ < 12 + || std::string(reinterpret_cast<char*>(header_.pData_), 9) + != std::string("Panasonic", 9)) { + rc = 2; + } + return rc; + } + + PanasonicMakerNote::AutoPtr PanasonicMakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + PanasonicMakerNote* PanasonicMakerNote::create_(bool alloc) const + { + AutoPtr makerNote = AutoPtr(new PanasonicMakerNote(alloc)); + assert(makerNote.get() != 0); + makerNote->readHeader(header_.pData_, header_.size_, byteOrder_); + return makerNote.release(); + } + + PanasonicMakerNote::AutoPtr PanasonicMakerNote::clone() const + { + return AutoPtr(clone_()); + } + + PanasonicMakerNote* PanasonicMakerNote::clone_() const + { + return new PanasonicMakerNote(*this); + } + + //! Quality + const TagDetails quality[] = { + { 0, "(start)" }, + { 2, "High" }, + { 3, "Standard" }, + { 6, "Very High" }, + { 7, "Raw" }, + { 0, "(end)" } + }; + + std::ostream& PanasonicMakerNote::print0x0001(std::ostream& os, + const Value& value) + { + return TagTranslator(quality).print(os, value); + } // PanasonicMakerNote::print0x0001 + + //! WhiteBalance + const TagDetails whiteBalance[] = { + { 0, "(start)" }, + { 1, "Auto" }, + { 2, "Daylight" }, + { 3, "Cloudy" }, + { 4, "Halogen" }, + { 5, "Manual" }, + { 8, "Flash" }, + { 10, "Black and White" }, + { 0, "(end)" } + }; + + std::ostream& PanasonicMakerNote::print0x0003(std::ostream& os, + const Value& value) + { + return TagTranslator(whiteBalance).print(os, value); + } // PanasonicMakerNote::print0x0003 + + //! FocusMode + const TagDetails focusMode[] = { + { 0, "(start)" }, + { 1, "Auto" }, + { 2, "Manual" }, + { 0, "(end)" } + }; + + std::ostream& PanasonicMakerNote::print0x0007(std::ostream& os, + const Value& value) + { + return TagTranslator(focusMode).print(os, value); + } // PanasonicMakerNote::print0x0007 + + std::ostream& PanasonicMakerNote::print0x000f(std::ostream& os, + const Value& value) + { + if (value.count() < 2 || value.typeId() != unsignedByte) { + return os << value; + } + long l0 = value.toLong(0); + if (l0 == 1) os << "On"; + else if (l0 == 16) os << "Off"; + else os << value; + return os; + } // PanasonicMakerNote::print0x000f + + //! ImageStabilizer + const TagDetails imageStabilizer[] = { + { 0, "(start)" }, + { 2, "On, Mode 1" }, + { 3, "Off" }, + { 4, "On, Mode 2" }, + { 0, "(end)" } + }; + + std::ostream& PanasonicMakerNote::print0x001a(std::ostream& os, + const Value& value) + { + return TagTranslator(imageStabilizer).print(os, value); + } // PanasonicMakerNote::print0x001a + + //! Macro + const TagDetails macro[] = { + { 0, "(start)" }, + { 1, "On" }, + { 2, "Off" }, + { 0, "(end)" } + }; + + std::ostream& PanasonicMakerNote::print0x001c(std::ostream& os, + const Value& value) + { + return TagTranslator(macro).print(os, value); + } // PanasonicMakerNote::print0x001c + + //! ShootingMode + const TagDetails shootingMode[] = { + { 0, "(start)" }, + { 1, "Normal" }, + { 2, "Portrait" }, + { 3, "Scenery" }, + { 4, "Sports" }, + { 5, "Night Portrait" }, + { 6, "Program" }, + { 7, "Aperture Priority" }, + { 8, "Shutter Priority" }, + { 9, "Macro" }, + { 11, "Manual" }, + { 13, "Panning" }, + { 18, "Fireworks" }, + { 19, "Party" }, + { 20, "Snow" }, + { 21, "Night Scenery" }, + { 0, "(end)" } + }; + + std::ostream& PanasonicMakerNote::print0x001f(std::ostream& os, + const Value& value) + { + return TagTranslator(shootingMode).print(os, value); + } // PanasonicMakerNote::print0x001f + + //! Audio + const TagDetails Audio[] = { + { 0, "(start)" }, + { 1, "Yes" }, + { 2, "No" }, + { 0, "(end)" } + }; + + std::ostream& PanasonicMakerNote::print0x0020(std::ostream& os, + const Value& value) + { + return TagTranslator(Audio).print(os, value); + } // PanasonicMakerNote::print0x0020 + + std::ostream& PanasonicMakerNote::print0x0023(std::ostream& os, + const Value& value) + { + return os << std::fixed << std::setprecision(1) + << value.toLong() / 3 << " EV"; + } // PanasonicMakerNote::print0x0023 + + //! ColorEffect + const TagDetails colorEffect[] = { + { 0, "(start)" }, + { 1, "Off" }, + { 2, "Warm" }, + { 3, "Cool" }, + { 4, "Black and White" }, + { 5, "Sepia" }, + { 0, "(end)" } + }; + + std::ostream& PanasonicMakerNote::print0x0028(std::ostream& os, + const Value& value) + { + return TagTranslator(colorEffect).print(os, value); + } // PanasonicMakerNote::print0x0028 + + //! Contrast + const TagDetails contrast[] = { + { -1, "(start)" }, + { 0, "Standard" }, + { 1, "Low" }, + { 2, "High" }, + { 0x100, "Low" }, + { 0x110, "Standard" }, + { 0x120, "High" }, + { -1, "(end)" } + }; + + std::ostream& PanasonicMakerNote::print0x002c(std::ostream& os, + const Value& value) + { + return TagTranslator(contrast).print(os, value); + } // PanasonicMakerNote::print0x002c + + //! NoiseReduction + const TagDetails noiseReduction[] = { + { -1, "(start)" }, + { 0, "Standard" }, + { 1, "Low" }, + { 2, "High" }, + { -1, "(end)" } + }; + + std::ostream& PanasonicMakerNote::print0x002d(std::ostream& os, + const Value& value) + { + return TagTranslator(noiseReduction).print(os, value); + } // PanasonicMakerNote::print0x002d + +// ***************************************************************************** +// free functions + + MakerNote::AutoPtr createPanasonicMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset) + { + return MakerNote::AutoPtr(new PanasonicMakerNote(alloc)); + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/panasonicmn.hpp b/src/plugins/exiv2/panasonicmn.hpp @@ -0,0 +1,170 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file panasonicmn.hpp + @brief Panasonic MakerNote implemented using the following references: + <a href="http://www.compton.nu/panasonic.html">Panasonic MakerNote Information</a> by Tom Hughes, + Panasonic.pm of <a href="http://www.sno.phy.queensu.ca/~phil/exiftool/">ExifTool</a> by Phil Harvey, + <a href="http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html">Panasonic Makernote Format Specification</a> by Evan Hunter. + @version $Rev: 581 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 11-Jun-05, ahu: created + */ +#ifndef PANASONICMN_HPP_ +#define PANASONICMN_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "makernote.hpp" +#include "tags.hpp" + +// + standard includes +#include <string> +#include <iosfwd> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class Value; + +// ***************************************************************************** +// free functions + + /*! + @brief Return an auto-pointer to a newly created empty MakerNote + initialized to operate in the memory management model indicated. + The caller owns this copy and the auto-pointer ensures that it + will be deleted. + + @param alloc Memory management model for the new MakerNote. Determines if + memory required to store data should be allocated and deallocated + (true) or not (false). If false, only pointers to the buffer + provided to read() will be kept. See Ifd for more background on + this concept. + @param buf Pointer to the makernote character buffer (not used). + @param len Length of the makernote character buffer (not used). + @param byteOrder Byte order in which the Exif data (and possibly the + makernote) is encoded (not used). + @param offset Offset from the start of the TIFF header of the makernote + buffer (not used). + + @return An auto-pointer to a newly created empty MakerNote. The caller + owns this copy and the auto-pointer ensures that it will be + deleted. + */ + MakerNote::AutoPtr createPanasonicMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset); + +// ***************************************************************************** +// class definitions + + //! MakerNote for Panasonic cameras + class PanasonicMakerNote : public IfdMakerNote { + public: + //! Shortcut for a %PanasonicMakerNote auto pointer. + typedef std::auto_ptr<PanasonicMakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to choose whether or not memory management + is required for the makernote entries. + */ + PanasonicMakerNote(bool alloc =true); + //! Copy constructor + PanasonicMakerNote(const PanasonicMakerNote& rhs); + //! Virtual destructor + virtual ~PanasonicMakerNote() {} + //@} + + //! @name Manipulators + //@{ + int readHeader(const byte* buf, + long len, + ByteOrder byteOrder); + //@} + + //! @name Accessors + //@{ + int checkHeader() const; + AutoPtr create(bool alloc =true) const; + AutoPtr clone() const; + //@} + + //! @name Print functions for Panasonic %MakerNote tags + //@{ + //! Print Quality + static std::ostream& print0x0001(std::ostream& os, const Value& value); + //! Print WhiteBalance + static std::ostream& print0x0003(std::ostream& os, const Value& value); + //! Print FocusMode + static std::ostream& print0x0007(std::ostream& os, const Value& value); + //! Print SpotMode + static std::ostream& print0x000f(std::ostream& os, const Value& value); + //! Print ImageStabilizer + static std::ostream& print0x001a(std::ostream& os, const Value& value); + //! Print Macro + static std::ostream& print0x001c(std::ostream& os, const Value& value); + //! Print ShootingMode + static std::ostream& print0x001f(std::ostream& os, const Value& value); + //! Print Audio + static std::ostream& print0x0020(std::ostream& os, const Value& value); + //! Print WhiteBalanceBias + static std::ostream& print0x0023(std::ostream& os, const Value& value); + //! Print ColorEffect + static std::ostream& print0x0028(std::ostream& os, const Value& value); + //! Print Contrast + static std::ostream& print0x002c(std::ostream& os, const Value& value); + //! Print NoiseReduction + static std::ostream& print0x002d(std::ostream& os, const Value& value); + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct RegisterMn { + RegisterMn(); + }; + //! @endcond + + private: + //! Internal virtual create function. + PanasonicMakerNote* create_(bool alloc =true) const; + //! Internal virtual copy constructor. + PanasonicMakerNote* clone_() const; + + //! Tag information + static const TagInfo tagInfo_[]; + + }; // class PanasonicMakerNote + + static PanasonicMakerNote::RegisterMn registerPanasonicMakerNote; +} // namespace Exiv2 + +#endif // #ifndef PANASONICMN_HPP_ diff --git a/src/plugins/exiv2/rcsid.hpp b/src/plugins/exiv2/rcsid.hpp @@ -0,0 +1,62 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file rcsid.hpp + @brief Define an RCS id string in every object file compiled from a source + file that includes rcsid.hpp. + + This is a simplified version of the ACE_RCSID macro that is used in the + <a href="http://www.cs.wustl.edu/~schmidt/ACE.html">ACE(TM)</a> distribution. + + @version $Rev: 538 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 02-Feb-04, ahu: created + */ +#ifndef RCSID_HPP_ +#define RCSID_HPP_ + +#if !defined (EXIV2_RCSID) +/*! + @brief Macro to store version information in each object file. + + Use this macro by including the following two lines at the beginning of + each *.cpp file. See the ident(1) manual pages for more information. + + @code + #include "rcsid.hpp" + EXIV2_RCSID("@(#) $Id$"); + @endcode + + The macro hack itself has the following purposes: + -# To define the RCS id string variable in the local namespace, so + that there won't be any duplicate extern symbols at link time. + -# To avoid warnings of the type "variable declared and never used". + + */ +#define EXIV2_RCSID(id) \ + namespace { \ + inline const char* getRcsId(const char*) { return id ; } \ + const char* rcsId = getRcsId(rcsId); \ + } + +#endif // #if !defined (EXIV2_RCSID) +#endif // #ifndef RCSID_HPP_ diff --git a/src/plugins/exiv2/sigmamn.cpp b/src/plugins/exiv2/sigmamn.cpp @@ -0,0 +1,211 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: sigmamn.cpp + Version: $Rev: 569 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 02-Apr-04, ahu: created + Credits: Sigma and Foveon MakerNote implemented according to the specification + in "SIGMA and FOVEON EXIF MakerNote Documentation" by Foveon. + <http://www.x3f.info/technotes/FileDocs/MakerNoteDoc.html> + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: sigmamn.cpp 569 2005-05-28 05:48:43Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "sigmamn.hpp" +#include "makernote.hpp" +#include "value.hpp" + +// + standard includes +#include <string> +#include <sstream> +#include <iomanip> +#include <cassert> + +// Define DEBUG_MAKERNOTE to output debug information to std::cerr +#undef DEBUG_MAKERNOTE + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + //! @cond IGNORE + SigmaMakerNote::RegisterMn::RegisterMn() + { + MakerNoteFactory::registerMakerNote("SIGMA", "*", createSigmaMakerNote); + MakerNoteFactory::registerMakerNote("FOVEON", "*", createSigmaMakerNote); + MakerNoteFactory::registerMakerNote( + sigmaIfdId, MakerNote::AutoPtr(new SigmaMakerNote)); + + ExifTags::registerMakerTagInfo(sigmaIfdId, tagInfo_); + } + //! @endcond + + // Sigma (Foveon) MakerNote Tag Info + const TagInfo SigmaMakerNote::tagInfo_[] = { + TagInfo(0x0002, "SerialNumber", "Camera serial number", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x0003, "DriveMode", "Drive Mode", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x0004, "ResolutionMode", "Resolution Mode", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x0005, "AutofocusMode", "Autofocus mode", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x0006, "FocusSetting", "Focus setting", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x0007, "WhiteBalance", "White balance", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x0008, "ExposureMode", "Exposure mode", sigmaIfdId, makerTags, asciiString, print0x0008), + TagInfo(0x0009, "MeteringMode", "Metering mode", sigmaIfdId, makerTags, asciiString, print0x0009), + TagInfo(0x000a, "LensRange", "Lens focal length range", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x000b, "ColorSpace", "Color space", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x000c, "Exposure", "Exposure", sigmaIfdId, makerTags, asciiString, printStripLabel), + TagInfo(0x000d, "Contrast", "Contrast", sigmaIfdId, makerTags, asciiString, printStripLabel), + TagInfo(0x000e, "Shadow", "Shadow", sigmaIfdId, makerTags, asciiString, printStripLabel), + TagInfo(0x000f, "Highlight", "Highlight", sigmaIfdId, makerTags, asciiString, printStripLabel), + TagInfo(0x0010, "Saturation", "Saturation", sigmaIfdId, makerTags, asciiString, printStripLabel), + TagInfo(0x0011, "Sharpness", "Sharpness", sigmaIfdId, makerTags, asciiString, printStripLabel), + TagInfo(0x0012, "FillLight", "X3 Fill light", sigmaIfdId, makerTags, asciiString, printStripLabel), + TagInfo(0x0014, "ColorAdjustment", "Color adjustment", sigmaIfdId, makerTags, asciiString, printStripLabel), + TagInfo(0x0015, "AdjustmentMode", "Adjustment mode", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x0016, "Quality", "Quality", sigmaIfdId, makerTags, asciiString, printStripLabel), + TagInfo(0x0017, "Firmware", "Firmware", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x0018, "Software", "Software", sigmaIfdId, makerTags, asciiString, printValue), + TagInfo(0x0019, "AutoBracket", "Auto bracket", sigmaIfdId, makerTags, asciiString, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownSigmaMakerNoteTag)", "Unknown SigmaMakerNote tag", sigmaIfdId, makerTags, invalidTypeId, printValue) + }; + + SigmaMakerNote::SigmaMakerNote(bool alloc) + : IfdMakerNote(sigmaIfdId, alloc) + { + byte buf[] = { + 'S', 'I', 'G', 'M', 'A', '\0', '\0', '\0', 0x01, 0x00 + }; + readHeader(buf, 10, byteOrder_); + } + + SigmaMakerNote::SigmaMakerNote(const SigmaMakerNote& rhs) + : IfdMakerNote(rhs) + { + } + + int SigmaMakerNote::readHeader(const byte* buf, + long len, + ByteOrder byteOrder) + { + if (len < 10) return 1; + + // Copy the header. My one and only Sigma sample has two undocumented + // extra bytes (0x01, 0x00) between the ID string and the start of the + // Makernote IFD. So we copy 10 bytes into the header. + header_.alloc(10); + memcpy(header_.pData_, buf, header_.size_); + // Adjust the offset of the IFD for the prefix + adjOffset_ = 10; + return 0; + } + + int SigmaMakerNote::checkHeader() const + { + int rc = 0; + // Check the SIGMA or FOVEON prefix + if ( header_.size_ < 10 + || std::string(reinterpret_cast<char*>(header_.pData_), 8) + != std::string("SIGMA\0\0\0", 8) + && std::string(reinterpret_cast<char*>(header_.pData_), 8) + != std::string("FOVEON\0\0", 8)) { + rc = 2; + } + return rc; + } + + SigmaMakerNote::AutoPtr SigmaMakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + SigmaMakerNote* SigmaMakerNote::create_(bool alloc) const + { + AutoPtr makerNote = AutoPtr(new SigmaMakerNote(alloc)); + assert(makerNote.get() != 0); + makerNote->readHeader(header_.pData_, header_.size_, byteOrder_); + return makerNote.release(); + } + + SigmaMakerNote::AutoPtr SigmaMakerNote::clone() const + { + return AutoPtr(clone_()); + } + + SigmaMakerNote* SigmaMakerNote::clone_() const + { + return new SigmaMakerNote(*this); + } + + std::ostream& SigmaMakerNote::printStripLabel(std::ostream& os, + const Value& value) + { + std::string v = value.toString(); + std::string::size_type pos = v.find(':'); + if (pos != std::string::npos) { + if (v[pos + 1] == ' ') ++pos; + v = v.substr(pos + 1); + } + return os << v; + } + + std::ostream& SigmaMakerNote::print0x0008(std::ostream& os, + const Value& value) + { + switch (value.toString()[0]) { + case 'P': os << "Program"; break; + case 'A': os << "Aperture priority"; break; + case 'S': os << "Shutter priority"; break; + case 'M': os << "Manual"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + + std::ostream& SigmaMakerNote::print0x0009(std::ostream& os, + const Value& value) + { + switch (value.toString()[0]) { + case 'A': os << "Average"; break; + case 'C': os << "Center"; break; + case '8': os << "8-Segment"; break; + default: os << "(" << value << ")"; break; + } + return os; + } + +// ***************************************************************************** +// free functions + + MakerNote::AutoPtr createSigmaMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset) + { + return MakerNote::AutoPtr(new SigmaMakerNote(alloc)); + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/sigmamn.hpp b/src/plugins/exiv2/sigmamn.hpp @@ -0,0 +1,151 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file sigmamn.hpp + @brief Sigma and Foveon MakerNote implemented according to the specification + <a href="http://www.x3f.info/technotes/FileDocs/MakerNoteDoc.html"> + SIGMA and FOVEON EXIF MakerNote Documentation</a> by Foveon. + @version $Rev: 569 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 02-Apr-04, ahu: created + */ +#ifndef SIGMAMN_HPP_ +#define SIGMAMN_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "makernote.hpp" +#include "tags.hpp" + +// + standard includes +#include <string> +#include <iosfwd> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class Value; + +// ***************************************************************************** +// free functions + + /*! + @brief Return an auto-pointer to a newly created empty MakerNote + initialized to operate in the memory management model indicated. + The caller owns this copy and the auto-pointer ensures that it + will be deleted. + + @param alloc Memory management model for the new MakerNote. Determines if + memory required to store data should be allocated and deallocated + (true) or not (false). If false, only pointers to the buffer + provided to read() will be kept. See Ifd for more background on + this concept. + @param buf Pointer to the makernote character buffer (not used). + @param len Length of the makernote character buffer (not used). + @param byteOrder Byte order in which the Exif data (and possibly the + makernote) is encoded (not used). + @param offset Offset from the start of the TIFF header of the makernote + buffer (not used). + + @return An auto-pointer to a newly created empty MakerNote. The caller + owns this copy and the auto-pointer ensures that it will be + deleted. + */ + MakerNote::AutoPtr createSigmaMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset); + +// ***************************************************************************** +// class definitions + + //! MakerNote for Sigma (Foveon) cameras + class SigmaMakerNote : public IfdMakerNote { + public: + //! Shortcut for a %SigmaMakerNote auto pointer. + typedef std::auto_ptr<SigmaMakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to choose whether or not memory management + is required for the makernote entries. + */ + SigmaMakerNote(bool alloc =true); + //! Copy constructor + SigmaMakerNote(const SigmaMakerNote& rhs); + //! Virtual destructor + virtual ~SigmaMakerNote() {} + //@} + + //! @name Manipulators + //@{ + int readHeader(const byte* buf, + long len, + ByteOrder byteOrder); + //@} + + //! @name Accessors + //@{ + int checkHeader() const; + AutoPtr create(bool alloc =true) const; + AutoPtr clone() const; + //@} + + //! @name Print functions for Sigma (Foveon) %MakerNote tags + //@{ + //! Strip the label from the value and print the remainder + static std::ostream& printStripLabel(std::ostream& os, const Value& value); + //! Print exposure mode + static std::ostream& print0x0008(std::ostream& os, const Value& value); + //! Print metering mode + static std::ostream& print0x0009(std::ostream& os, const Value& value); + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct RegisterMn { + RegisterMn(); + }; + //! @endcond + + private: + //! Internal virtual create function. + SigmaMakerNote* create_(bool alloc =true) const; + //! Internal virtual copy constructor. + SigmaMakerNote* clone_() const; + + //! Tag information + static const TagInfo tagInfo_[]; + + }; // class SigmaMakerNote + + static SigmaMakerNote::RegisterMn registerSigmaMakerNote; +} // namespace Exiv2 + +#endif // #ifndef SIGMAMN_HPP_ diff --git a/src/plugins/exiv2/sonymn.cpp b/src/plugins/exiv2/sonymn.cpp @@ -0,0 +1,150 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: sonymn.cpp + Version: $Rev: 569 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 18-Apr-05, ahu: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: sonymn.cpp 569 2005-05-28 05:48:43Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "sonymn.hpp" +#include "makernote.hpp" +#include "value.hpp" + +// + standard includes +#include <string> +#include <sstream> +#include <iomanip> +#include <cassert> + +// Define DEBUG_MAKERNOTE to output debug information to std::cerr +#undef DEBUG_MAKERNOTE + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + //! @cond IGNORE + SonyMakerNote::RegisterMn::RegisterMn() + { + MakerNoteFactory::registerMakerNote("SONY", "*", createSonyMakerNote); + MakerNoteFactory::registerMakerNote( + sonyIfdId, MakerNote::AutoPtr(new SonyMakerNote)); + + ExifTags::registerMakerTagInfo(sonyIfdId, tagInfo_); + } + //! @endcond + + // Sony MakerNote Tag Info + const TagInfo SonyMakerNote::tagInfo_[] = { + TagInfo(0x2000, "0x2000", "Unknown", sonyIfdId, makerTags, undefined, printValue), + TagInfo(0x9001, "0x9001", "Unknown", sonyIfdId, makerTags, undefined, printValue), + TagInfo(0x9002, "0x9002", "Unknown", sonyIfdId, makerTags, undefined, printValue), + TagInfo(0x9003, "0x9003", "Unknown", sonyIfdId, makerTags, undefined, printValue), + TagInfo(0x9004, "0x9004", "Unknown", sonyIfdId, makerTags, undefined, printValue), + TagInfo(0x9005, "0x9005", "Unknown", sonyIfdId, makerTags, undefined, printValue), + TagInfo(0x9006, "0x9006", "Unknown", sonyIfdId, makerTags, undefined, printValue), + TagInfo(0x9007, "0x9007", "Unknown", sonyIfdId, makerTags, undefined, printValue), + TagInfo(0x9008, "0x9008", "Unknown", sonyIfdId, makerTags, undefined, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownSonyMakerNoteTag)", "Unknown SonyMakerNote tag", sonyIfdId, makerTags, invalidTypeId, printValue) + }; + + SonyMakerNote::SonyMakerNote(bool alloc) + : IfdMakerNote(sonyIfdId, alloc, false) + { + byte buf[] = { + 'S', 'O', 'N', 'Y', ' ', 'D', 'S', 'C', ' ', '\0', '\0', '\0' + }; + readHeader(buf, 12, byteOrder_); + } + + SonyMakerNote::SonyMakerNote(const SonyMakerNote& rhs) + : IfdMakerNote(rhs) + { + } + + int SonyMakerNote::readHeader(const byte* buf, + long len, + ByteOrder byteOrder) + { + if (len < 12) return 1; + header_.alloc(12); + memcpy(header_.pData_, buf, header_.size_); + // Adjust the offset of the IFD for the prefix + adjOffset_ = 12; + return 0; + } + + int SonyMakerNote::checkHeader() const + { + int rc = 0; + // Check the SONY prefix + if ( header_.size_ < 12 + || std::string(reinterpret_cast<char*>(header_.pData_), 12) + != std::string("SONY DSC \0\0\0", 12)) { + rc = 2; + } + return rc; + } + + SonyMakerNote::AutoPtr SonyMakerNote::create(bool alloc) const + { + return AutoPtr(create_(alloc)); + } + + SonyMakerNote* SonyMakerNote::create_(bool alloc) const + { + AutoPtr makerNote = AutoPtr(new SonyMakerNote(alloc)); + assert(makerNote.get() != 0); + makerNote->readHeader(header_.pData_, header_.size_, byteOrder_); + return makerNote.release(); + } + + SonyMakerNote::AutoPtr SonyMakerNote::clone() const + { + return AutoPtr(clone_()); + } + + SonyMakerNote* SonyMakerNote::clone_() const + { + return new SonyMakerNote(*this); + } + +// ***************************************************************************** +// free functions + + MakerNote::AutoPtr createSonyMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset) + { + return MakerNote::AutoPtr(new SonyMakerNote(alloc)); + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/sonymn.hpp b/src/plugins/exiv2/sonymn.hpp @@ -0,0 +1,139 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file sonymn.hpp + @brief Basic Sony MakerNote implementation + @version $Rev: 569 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 18-Apr-05, ahu: created + */ +#ifndef SONYMN_HPP_ +#define SONYMN_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" +#include "makernote.hpp" +#include "tags.hpp" + +// + standard includes +#include <string> +#include <iosfwd> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class Value; + +// ***************************************************************************** +// free functions + + /*! + @brief Return an auto-pointer to a newly created empty MakerNote + initialized to operate in the memory management model indicated. + The caller owns this copy and the auto-pointer ensures that it + will be deleted. + + @param alloc Memory management model for the new MakerNote. Determines if + memory required to store data should be allocated and deallocated + (true) or not (false). If false, only pointers to the buffer + provided to read() will be kept. See Ifd for more background on + this concept. + @param buf Pointer to the makernote character buffer (not used). + @param len Length of the makernote character buffer (not used). + @param byteOrder Byte order in which the Exif data (and possibly the + makernote) is encoded (not used). + @param offset Offset from the start of the TIFF header of the makernote + buffer (not used). + + @return An auto-pointer to a newly created empty MakerNote. The caller + owns this copy and the auto-pointer ensures that it will be + deleted. + */ + MakerNote::AutoPtr createSonyMakerNote(bool alloc, + const byte* buf, + long len, + ByteOrder byteOrder, + long offset); + +// ***************************************************************************** +// class definitions + + //! MakerNote for Sony cameras + class SonyMakerNote : public IfdMakerNote { + public: + //! Shortcut for a %SonyMakerNote auto pointer. + typedef std::auto_ptr<SonyMakerNote> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor. Allows to choose whether or not memory management + is required for the makernote entries. + */ + SonyMakerNote(bool alloc =true); + //! Copy constructor + SonyMakerNote(const SonyMakerNote& rhs); + //! Virtual destructor + virtual ~SonyMakerNote() {} + //@} + + //! @name Manipulators + //@{ + int readHeader(const byte* buf, + long len, + ByteOrder byteOrder); + //@} + + //! @name Accessors + //@{ + int checkHeader() const; + AutoPtr create(bool alloc =true) const; + AutoPtr clone() const; + //@} + + //! @cond IGNORE + // Public only so that we can create a static instance + struct RegisterMn { + RegisterMn(); + }; + //! @endcond + + private: + //! Internal virtual create function. + SonyMakerNote* create_(bool alloc =true) const; + //! Internal virtual copy constructor. + SonyMakerNote* clone_() const; + + //! Tag information + static const TagInfo tagInfo_[]; + + }; // class SonyMakerNote + + static SonyMakerNote::RegisterMn registerSonyMakerNote; +} // namespace Exiv2 + +#endif // #ifndef SONYMN_HPP_ diff --git a/src/plugins/exiv2/taglist.cpp b/src/plugins/exiv2/taglist.cpp @@ -0,0 +1,69 @@ +// ***************************************************************** -*- C++ -*- +/* + Abstract: Print a simple comma separated list of tags defined in Exiv2 + + File: taglist.cpp + Version: $Rev: 570 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 07-Jan-04, ahu: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: taglist.cpp 570 2005-05-28 15:35:08Z ahuggel $"); + +#include "makernote.hpp" +#include "tags.hpp" +#include "datasets.hpp" +#include "error.hpp" + +#include <string> +#include <iostream> + +using namespace Exiv2; + +int main(int argc, char* argv[]) +try { + int rc = 0; + + switch (argc) { + case 2: + { + std::string item(argv[1]); + + if (item == "Exif") { + ExifTags::taglist(std::cout); + break; + } + + if (item == "Iptc") { + IptcDataSets::dataSetList(std::cout); + break; + } + + IfdId ifdId = ExifTags::ifdIdByIfdItem(item); + if (ExifTags::isMakerIfd(ifdId)) { + ExifTags::makerTaglist(std::cout, ifdId); + } + else { + rc = 2; + } + break; + } + case 1: + ExifTags::taglist(std::cout); + break; + default: + rc = 1; + break; + } + if (rc) { + std::cout << "Usage: " << argv[0] + << " [Exif|Canon|CanonCs1|CanonCs2|CanonCf|Fujifilm|Nikon1|Nikon2|Nikon3|Olympus|Sigma|Sony|Iptc]\n" + << "Print Exif tags, MakerNote tags, or Iptc datasets\n"; + } + return rc; +} +catch (AnyError& e) { + std::cout << "Caught Exiv2 exception '" << e << "'\n"; + return 1; +} diff --git a/src/plugins/exiv2/tags.cpp b/src/plugins/exiv2/tags.cpp @@ -0,0 +1,1230 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: tags.cpp + Version: $Rev: 581 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 15-Jan-04, ahu: created + 21-Jan-05, ahu: added MakerNote TagInfo registry and related code + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: tags.cpp 581 2005-06-12 05:54:57Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "tags.hpp" +#include "error.hpp" +#include "types.hpp" +#include "ifd.hpp" +#include "value.hpp" +#include "makernote.hpp" +#include "mn.hpp" // To ensure that all makernotes are registered + +#include <iostream> +#include <iomanip> +#include <sstream> +#include <utility> +#include <cstdlib> +#include <cassert> + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + IfdInfo::IfdInfo(IfdId ifdId, const char* name, const char* item) + : ifdId_(ifdId), name_(name), item_(item) + { + } + + // Todo: Allow to register new IfdInfo entries from elsewhere (the makernotes) + // Important: IFD item must be unique! + const IfdInfo ExifTags::ifdInfo_[] = { + IfdInfo(ifdIdNotSet, "(Unknown IFD)", "(Unknown item)"), + IfdInfo(ifd0Id, "IFD0", "Image"), + IfdInfo(exifIfdId, "Exif", "Photo"), // just to avoid 'Exif.Exif.*' keys + IfdInfo(gpsIfdId, "GPSInfo", "GPSInfo"), + IfdInfo(iopIfdId, "Iop", "Iop"), + IfdInfo(ifd1Id, "IFD1", "Thumbnail"), + IfdInfo(canonIfdId, "Makernote", "Canon"), + IfdInfo(canonCs1IfdId, "Makernote", "CanonCs1"), + IfdInfo(canonCs2IfdId, "Makernote", "CanonCs2"), + IfdInfo(canonCfIfdId, "Makernote", "CanonCf"), + IfdInfo(fujiIfdId, "Makernote", "Fujifilm"), + IfdInfo(nikon1IfdId, "Makernote", "Nikon1"), + IfdInfo(nikon2IfdId, "Makernote", "Nikon2"), + IfdInfo(nikon3IfdId, "Makernote", "Nikon3"), + IfdInfo(olympusIfdId, "Makernote", "Olympus"), + IfdInfo(panasonicIfdId, "Makernote", "Panasonic"), + IfdInfo(sigmaIfdId, "Makernote", "Sigma"), + IfdInfo(sonyIfdId, "Makernote", "Sony"), + IfdInfo(lastIfdId, "(Last IFD info)", "(Last IFD item)") + }; + + SectionInfo::SectionInfo( + SectionId sectionId, + const char* name, + const char* desc + ) + : sectionId_(sectionId), name_(name), desc_(desc) + { + } + + const SectionInfo ExifTags::sectionInfo_[] = { + SectionInfo(sectionIdNotSet, "(UnknownSection)", "Unknown section"), + SectionInfo(imgStruct, "ImageStructure", "Image data structure"), + SectionInfo(recOffset, "RecordingOffset", "Recording offset"), + SectionInfo(imgCharacter, "ImageCharacteristics", "Image data characteristics"), + SectionInfo(otherTags, "OtherTags", "Other data"), + SectionInfo(exifFormat, "ExifFormat", "Exif data structure"), + SectionInfo(exifVersion, "ExifVersion", "Exif Version"), + SectionInfo(imgConfig, "ImageConfig", "Image configuration"), + SectionInfo(userInfo, "UserInfo", "User information"), + SectionInfo(relatedFile, "RelatedFile", "Related file"), + SectionInfo(dateTime, "DateTime", "Date and time"), + SectionInfo(captureCond, "CaptureConditions", "Picture taking conditions"), + SectionInfo(gpsTags, "GPS", "GPS information"), + SectionInfo(iopTags, "Interoperability", "Interoperability information"), + SectionInfo(makerTags, "Makernote", "Vendor specific information"), + SectionInfo(lastSectionId, "(LastSection)", "Last section") + }; + + TagInfo::TagInfo( + uint16_t tag, + const char* name, + const char* desc, + IfdId ifdId, + SectionId sectionId, + TypeId typeId, + PrintFct printFct + ) + : tag_(tag), name_(name), desc_(desc), ifdId_(ifdId), + sectionId_(sectionId), typeId_(typeId), printFct_(printFct) + { + } + + // Base IFD Tags (IFD0 and IFD1) + static const TagInfo ifdTagInfo[] = { + TagInfo(0x0100, "ImageWidth", "Image width", ifd0Id, imgStruct, unsignedLong, printValue), + TagInfo(0x0101, "ImageLength", "Image height", ifd0Id, imgStruct, unsignedLong, printValue), + TagInfo(0x0102, "BitsPerSample", "Number of bits per component", ifd0Id, imgStruct, unsignedShort, printValue), + TagInfo(0x0103, "Compression", "Compression scheme", ifd0Id, imgStruct, unsignedShort, print0x0103), + TagInfo(0x0106, "PhotometricInterpretation", "Pixel composition", ifd0Id, imgStruct, unsignedShort, print0x0106), + TagInfo(0x010e, "ImageDescription", "Image title", ifd0Id, otherTags, asciiString, printValue), + TagInfo(0x010f, "Make", "Manufacturer of image input equipment", ifd0Id, otherTags, asciiString, printValue), + TagInfo(0x0110, "Model", "Model of image input equipment", ifd0Id, otherTags, asciiString, printValue), + TagInfo(0x0111, "StripOffsets", "Image data location", ifd0Id, recOffset, unsignedLong, printValue), + TagInfo(0x0112, "Orientation", "Orientation of image", ifd0Id, imgStruct, unsignedShort, print0x0112), + TagInfo(0x0115, "SamplesPerPixel", "Number of components", ifd0Id, imgStruct, unsignedShort, printValue), + TagInfo(0x0116, "RowsPerStrip", "Number of rows per strip", ifd0Id, recOffset, unsignedLong, printValue), + TagInfo(0x0117, "StripByteCounts", "Bytes per compressed strip", ifd0Id, recOffset, unsignedLong, printValue), + TagInfo(0x011a, "XResolution", "Image resolution in width direction", ifd0Id, imgStruct, unsignedRational, printLong), + TagInfo(0x011b, "YResolution", "Image resolution in height direction", ifd0Id, imgStruct, unsignedRational, printLong), + TagInfo(0x011c, "PlanarConfiguration", "Image data arrangement", ifd0Id, imgStruct, unsignedShort, printValue), + TagInfo(0x0128, "ResolutionUnit", "Unit of X and Y resolution", ifd0Id, imgStruct, unsignedShort, printUnit), + TagInfo(0x012d, "TransferFunction", "Transfer function", ifd0Id, imgCharacter, unsignedShort, printValue), + TagInfo(0x0131, "Software", "Software used", ifd0Id, otherTags, asciiString, printValue), + TagInfo(0x0132, "DateTime", "File change date and time", ifd0Id, otherTags, asciiString, printValue), + TagInfo(0x013b, "Artist", "Person who created the image", ifd0Id, otherTags, asciiString, printValue), + TagInfo(0x013e, "WhitePoint", "White point chromaticity", ifd0Id, imgCharacter, unsignedRational, printValue), + TagInfo(0x013f, "PrimaryChromaticities", "Chromaticities of primaries", ifd0Id, imgCharacter, unsignedRational, printValue), + TagInfo(0x0201, "JPEGInterchangeFormat", "Offset to JPEG SOI", ifd0Id, recOffset, unsignedLong, printValue), + TagInfo(0x0202, "JPEGInterchangeFormatLength", "Bytes of JPEG data", ifd0Id, recOffset, unsignedLong, printValue), + TagInfo(0x0211, "YCbCrCoefficients", "Color space transformation matrix coefficients", ifd0Id, imgCharacter, unsignedRational, printValue), + TagInfo(0x0212, "YCbCrSubSampling", "Subsampling ratio of Y to C", ifd0Id, imgStruct, unsignedShort, printValue), + TagInfo(0x0213, "YCbCrPositioning", "Y and C positioning", ifd0Id, imgStruct, unsignedShort, print0x0213), + TagInfo(0x0214, "ReferenceBlackWhite", "Pair of black and white reference values", ifd0Id, imgCharacter, unsignedRational, printValue), + TagInfo(0x8298, "Copyright", "Copyright holder", ifd0Id, otherTags, asciiString, print0x8298), + TagInfo(0x8769, "ExifTag", "Exif IFD Pointer", ifd0Id, exifFormat, unsignedLong, printValue), + TagInfo(0x8825, "GPSTag", "GPSInfo IFD Pointer", ifd0Id, exifFormat, unsignedLong, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownIfdTag)", "Unknown IFD tag", ifdIdNotSet, sectionIdNotSet, invalidTypeId, printValue) + }; + + // Exif IFD Tags + static const TagInfo exifTagInfo[] = { + TagInfo(0x829a, "ExposureTime", "Exposure time", exifIfdId, captureCond, unsignedRational, print0x829a), + TagInfo(0x829d, "FNumber", "F number", exifIfdId, captureCond, unsignedRational, print0x829d), + TagInfo(0x8822, "ExposureProgram", "Exposure program", exifIfdId, captureCond, unsignedShort, print0x8822), + TagInfo(0x8824, "SpectralSensitivity", "Spectral sensitivity", exifIfdId, captureCond, asciiString, printValue), + TagInfo(0x8827, "ISOSpeedRatings", "ISO speed ratings", exifIfdId, captureCond, unsignedShort, print0x8827), + TagInfo(0x8828, "OECF", "Optoelectric coefficient", exifIfdId, captureCond, undefined, printValue), + TagInfo(0x9000, "ExifVersion", "Exif Version", exifIfdId, exifVersion, undefined, printValue), + TagInfo(0x9003, "DateTimeOriginal", "Date and time original image was generated", exifIfdId, dateTime, asciiString, printValue), + TagInfo(0x9004, "DateTimeDigitized", "Date and time image was made digital data", exifIfdId, dateTime, asciiString, printValue), + TagInfo(0x9101, "ComponentsConfiguration", "Meaning of each component", exifIfdId, imgConfig, undefined, print0x9101), + TagInfo(0x9102, "CompressedBitsPerPixel", "Image compression mode", exifIfdId, imgConfig, unsignedRational, printFloat), + TagInfo(0x9201, "ShutterSpeedValue", "Shutter speed", exifIfdId, captureCond, signedRational, printFloat), + TagInfo(0x9202, "ApertureValue", "Aperture", exifIfdId, captureCond, unsignedRational, printFloat), + TagInfo(0x9203, "BrightnessValue", "Brightness", exifIfdId, captureCond, signedRational, printFloat), + TagInfo(0x9204, "ExposureBiasValue", "Exposure bias", exifIfdId, captureCond, signedRational, print0x9204), + TagInfo(0x9205, "MaxApertureValue", "Maximum lens aperture", exifIfdId, captureCond, unsignedRational, printFloat), + TagInfo(0x9206, "SubjectDistance", "Subject distance", exifIfdId, captureCond, unsignedRational, print0x9206), + TagInfo(0x9207, "MeteringMode", "Metering mode", exifIfdId, captureCond, unsignedShort, print0x9207), + TagInfo(0x9208, "LightSource", "Light source", exifIfdId, captureCond, unsignedShort, print0x9208), + TagInfo(0x9209, "Flash", "Flash", exifIfdId, captureCond, unsignedShort, print0x9209), + TagInfo(0x920a, "FocalLength", "Lens focal length", exifIfdId, captureCond, unsignedRational, print0x920a), + TagInfo(0x9214, "SubjectArea", "Subject area", exifIfdId, captureCond, unsignedShort, printValue), + TagInfo(0x927c, "MakerNote", "Manufacturer notes", exifIfdId, userInfo, undefined, printValue), + TagInfo(0x9286, "UserComment", "User comments", exifIfdId, userInfo, comment, print0x9286), + TagInfo(0x9290, "SubSecTime", "DateTime subseconds", exifIfdId, dateTime, asciiString, printValue), + TagInfo(0x9291, "SubSecTimeOriginal", "DateTimeOriginal subseconds", exifIfdId, dateTime, asciiString, printValue), + TagInfo(0x9292, "SubSecTimeDigitized", "DateTimeDigitized subseconds", exifIfdId, dateTime, asciiString, printValue), + TagInfo(0xa000, "FlashpixVersion", "Supported Flashpix version", exifIfdId, exifVersion, undefined, printValue), + TagInfo(0xa001, "ColorSpace", "Color space information", exifIfdId, imgCharacter, unsignedShort, print0xa001), + TagInfo(0xa002, "PixelXDimension", "Valid image width", exifIfdId, imgConfig, unsignedLong, printValue), + TagInfo(0xa003, "PixelYDimension", "Valid image height", exifIfdId, imgConfig, unsignedLong, printValue), + TagInfo(0xa004, "RelatedSoundFile", "Related audio file", exifIfdId, relatedFile, asciiString, printValue), + TagInfo(0xa005, "InteroperabilityTag", "Interoperability IFD Pointer", exifIfdId, exifFormat, unsignedLong, printValue), + TagInfo(0xa20b, "FlashEnergy", "Flash energy", exifIfdId, captureCond, unsignedRational, printValue), + TagInfo(0xa20c, "SpatialFrequencyResponse", "Spatial frequency response", exifIfdId, captureCond, undefined, printValue), + TagInfo(0xa20e, "FocalPlaneXResolution", "Focal plane X resolution", exifIfdId, captureCond, unsignedRational, printFloat), + TagInfo(0xa20f, "FocalPlaneYResolution", "Focal plane Y resolution", exifIfdId, captureCond, unsignedRational, printFloat), + TagInfo(0xa210, "FocalPlaneResolutionUnit", "Focal plane resolution unit", exifIfdId, captureCond, unsignedShort, printUnit), + TagInfo(0xa214, "SubjectLocation", "Subject location", exifIfdId, captureCond, unsignedShort, printValue), + TagInfo(0xa215, "ExposureIndex", "Exposure index", exifIfdId, captureCond, unsignedRational, printValue), + TagInfo(0xa217, "SensingMethod", "Sensing method", exifIfdId, captureCond, unsignedShort, print0xa217), + TagInfo(0xa300, "FileSource", "File source", exifIfdId, captureCond, undefined, print0xa300), + TagInfo(0xa301, "SceneType", "Scene type", exifIfdId, captureCond, undefined, print0xa301), + TagInfo(0xa302, "CFAPattern", "CFA pattern", exifIfdId, captureCond, undefined, printValue), + TagInfo(0xa401, "CustomRendered", "Custom image processing", exifIfdId, captureCond, unsignedShort, printValue), + TagInfo(0xa402, "ExposureMode", "Exposure mode", exifIfdId, captureCond, unsignedShort, print0xa402), + TagInfo(0xa403, "WhiteBalance", "White balance", exifIfdId, captureCond, unsignedShort, print0xa403), + TagInfo(0xa404, "DigitalZoomRatio", "Digital zoom ratio", exifIfdId, captureCond, unsignedRational, print0xa404), + TagInfo(0xa405, "FocalLengthIn35mmFilm", "Focal length in 35 mm film", exifIfdId, captureCond, unsignedShort, print0xa405), + TagInfo(0xa406, "SceneCaptureType", "Scene capture type", exifIfdId, captureCond, unsignedShort, print0xa406), + TagInfo(0xa407, "GainControl", "Gain control", exifIfdId, captureCond, unsignedRational, print0xa407), + TagInfo(0xa408, "Contrast", "Contrast", exifIfdId, captureCond, unsignedShort, print0xa408), + TagInfo(0xa409, "Saturation", "Saturation", exifIfdId, captureCond, unsignedShort, print0xa409), + TagInfo(0xa40a, "Sharpness", "Sharpness", exifIfdId, captureCond, unsignedShort, print0xa40a), + TagInfo(0xa40b, "DeviceSettingDescription", "Device settings description", exifIfdId, captureCond, undefined, printValue), + TagInfo(0xa40c, "SubjectDistanceRange", "Subject distance range", exifIfdId, captureCond, unsignedShort, print0xa40c), + TagInfo(0xa420, "ImageUniqueID", "Unique image ID", exifIfdId, otherTags, asciiString, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownExifTag)", "Unknown Exif tag", ifdIdNotSet, sectionIdNotSet, invalidTypeId, printValue) + }; + + // GPS Info Tags + static const TagInfo gpsTagInfo[] = { + TagInfo(0x0000, "GPSVersionID", "GPS tag version", gpsIfdId, gpsTags, unsignedByte, printValue), + TagInfo(0x0001, "GPSLatitudeRef", "North or South Latitude", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x0002, "GPSLatitude", "Latitude", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x0003, "GPSLongitudeRef", "East or West Longitude", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x0004, "GPSLongitude", "Longitude", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x0005, "GPSAltitudeRef", "Altitude reference", gpsIfdId, gpsTags, unsignedByte, printValue), + TagInfo(0x0006, "GPSAltitude", "Altitude", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x0007, "GPSTimeStamp", "GPS time (atomic clock)", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x0008, "GPSSatellites", "GPS satellites used for measurement", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x0009, "GPSStatus", "GPS receiver status", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x000a, "GPSMeasureMode", "GPS measurement mode", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x000b, "GPSDOP", "Measurement precision", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x000c, "GPSSpeedRef", "Speed unit", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x000d, "GPSSpeed", "Speed of GPS receiver", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x000e, "GPSTrackRef", "Reference for direction of movement", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x000f, "GPSTrack", "Direction of movement", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x0010, "GPSImgDirectionRef", "Reference for direction of image", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x0011, "GPSImgDirection", "Direction of image", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x0012, "GPSMapDatum", "Geodetic survey data used", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x0013, "GPSDestLatitudeRef", "Reference for latitude of destination", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x0014, "GPSDestLatitude", "Latitude of destination", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x0015, "GPSDestLongitudeRef", "Reference for longitude of destination", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x0016, "GPSDestLongitude", "Longitude of destination", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x0017, "GPSDestBearingRef", "Reference for bearing of destination", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x0018, "GPSDestBearing", "Bearing of destination", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x0019, "GPSDestDistanceRef", "Reference for distance to destination", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x001a, "GPSDestDistance", "Distance to destination", gpsIfdId, gpsTags, unsignedRational, printValue), + TagInfo(0x001b, "GPSProcessingMethod", "Name of GPS processing method", gpsIfdId, gpsTags, undefined, printValue), + TagInfo(0x001c, "GPSAreaInformation", "Name of GPS area", gpsIfdId, gpsTags, undefined, printValue), + TagInfo(0x001d, "GPSDateStamp", "GPS date", gpsIfdId, gpsTags, asciiString, printValue), + TagInfo(0x001e, "GPSDifferential", "GPS differential correction", gpsIfdId, gpsTags, unsignedShort, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownGpsTag)", "Unknown GPSInfo tag", ifdIdNotSet, sectionIdNotSet, invalidTypeId, printValue) + }; + + // Exif Interoperability IFD Tags + static const TagInfo iopTagInfo[] = { + TagInfo(0x0001, "InteroperabilityIndex", "Interoperability Identification", iopIfdId, iopTags, asciiString, printValue), + TagInfo(0x0002, "InteroperabilityVersion", "Interoperability version", iopIfdId, iopTags, undefined, printValue), + TagInfo(0x1000, "RelatedImageFileFormat", "File format of image file", iopIfdId, iopTags, asciiString, printValue), + TagInfo(0x1001, "RelatedImageWidth", "Image width", iopIfdId, iopTags, unsignedLong, printValue), + TagInfo(0x1002, "RelatedImageLength", "Image height", iopIfdId, iopTags, unsignedLong, printValue), + // End of list marker + TagInfo(0xffff, "(UnknownIopTag)", "Unknown Exif Interoperability tag", ifdIdNotSet, sectionIdNotSet, invalidTypeId, printValue) + }; + + // Unknown Tag + static const TagInfo unknownTag(0xffff, "Unknown tag", "Unknown tag", ifdIdNotSet, sectionIdNotSet, asciiString, printValue); + + std::ostream& TagTranslator::print(std::ostream& os, const Value& value) const + { + if (!pTagDetails_) return os << value; + + long l = value.toLong(); + + long e = pTagDetails_[0].val_; + int i = 1; + for (; pTagDetails_[i].val_ != l && pTagDetails_[i].val_ != e; ++i) {} + if (pTagDetails_[i].val_ == l) { + os << pTagDetails_[i].label_; + } + else { + os << "(" << l << ")"; + } + return os; + } // TagTranslator::print + + // Tag lookup lists with tag names, desc and where they (preferably) belong to; + // this is an array with pointers to one list per IFD. The IfdId is used as the + // index into the array. + const TagInfo* ExifTags::tagInfos_[] = { + 0, + ifdTagInfo, exifTagInfo, gpsTagInfo, iopTagInfo, ifdTagInfo, + 0 + }; + + // Lookup list for registered makernote tag info tables + const TagInfo* ExifTags::makerTagInfos_[]; + + // All makernote ifd ids, in the same order as the tag infos in makerTagInfos_ + IfdId ExifTags::makerIfdIds_[]; + + void ExifTags::registerBaseTagInfo(IfdId ifdId) + { + registerMakerTagInfo(ifdId, ifdTagInfo); + } + + void ExifTags::registerMakerTagInfo(IfdId ifdId, const TagInfo* tagInfo) + { + int i = 0; + for (; i < MAX_MAKER_TAG_INFOS; ++i) { + if (makerIfdIds_[i] == 0) { + makerIfdIds_[i] = ifdId; + makerTagInfos_[i] = tagInfo; + break; + } + } + if (i == MAX_MAKER_TAG_INFOS) throw Error(16); + } // ExifTags::registerMakerTagInfo + + int ExifTags::tagInfoIdx(uint16_t tag, IfdId ifdId) + { + const TagInfo* tagInfo = tagInfos_[ifdId]; + if (tagInfo == 0) return -1; + int idx; + for (idx = 0; tagInfo[idx].tag_ != 0xffff; ++idx) { + if (tagInfo[idx].tag_ == tag) return idx; + } + return -1; + } // ExifTags::tagInfoIdx + + const TagInfo* ExifTags::makerTagInfo(uint16_t tag, IfdId ifdId) + { + int i = 0; + for (; i < MAX_MAKER_TAG_INFOS && makerIfdIds_[i] != ifdId; ++i); + if (i == MAX_MAKER_TAG_INFOS) return 0; + + for (int k = 0; makerTagInfos_[i][k].tag_ != 0xffff; ++k) { + if (makerTagInfos_[i][k].tag_ == tag) return &makerTagInfos_[i][k]; + } + + return 0; + } // ExifTags::makerTagInfo + + const TagInfo* ExifTags::makerTagInfo(const std::string& tagName, + IfdId ifdId) + { + int i = 0; + for (; i < MAX_MAKER_TAG_INFOS && makerIfdIds_[i] != ifdId; ++i); + if (i == MAX_MAKER_TAG_INFOS) return 0; + + for (int k = 0; makerTagInfos_[i][k].tag_ != 0xffff; ++k) { + if (makerTagInfos_[i][k].name_ == tagName) { + return &makerTagInfos_[i][k]; + } + } + + return 0; + } // ExifTags::makerTagInfo + + bool ExifTags::isMakerIfd(IfdId ifdId) + { + int i = 0; + for (; i < MAX_MAKER_TAG_INFOS && makerIfdIds_[i] != ifdId; ++i); + return i != MAX_MAKER_TAG_INFOS && makerIfdIds_[i] != IfdId(0); + } + + std::string ExifTags::tagName(uint16_t tag, IfdId ifdId) + { + if (isExifIfd(ifdId)) { + int idx = tagInfoIdx(tag, ifdId); + if (idx != -1) return tagInfos_[ifdId][idx].name_; + } + if (isMakerIfd(ifdId)) { + const TagInfo* tagInfo = makerTagInfo(tag, ifdId); + if (tagInfo != 0) return tagInfo->name_; + } + std::ostringstream os; + os << "0x" << std::setw(4) << std::setfill('0') << std::right + << std::hex << tag; + return os.str(); + } // ExifTags::tagName + + const char* ExifTags::tagDesc(uint16_t tag, IfdId ifdId) + { + if (isExifIfd(ifdId)) { + int idx = tagInfoIdx(tag, ifdId); + if (idx == -1) return unknownTag.desc_; + return tagInfos_[ifdId][idx].desc_; + } + if (isMakerIfd(ifdId)) { + const TagInfo* tagInfo = makerTagInfo(tag, ifdId); + if (tagInfo != 0) return tagInfo->desc_; + } + return ""; + } // ExifTags::tagDesc + + const char* ExifTags::sectionName(uint16_t tag, IfdId ifdId) + { + if (isExifIfd(ifdId)) { + int idx = tagInfoIdx(tag, ifdId); + if (idx == -1) return sectionInfo_[unknownTag.sectionId_].name_; + const TagInfo* tagInfo = tagInfos_[ifdId]; + return sectionInfo_[tagInfo[idx].sectionId_].name_; + } + if (isMakerIfd(ifdId)) { + const TagInfo* tagInfo = makerTagInfo(tag, ifdId); + if (tagInfo != 0) return sectionInfo_[tagInfo->sectionId_].name_; + } + return ""; + } // ExifTags::sectionName + + const char* ExifTags::sectionDesc(uint16_t tag, IfdId ifdId) + { + if (isExifIfd(ifdId)) { + int idx = tagInfoIdx(tag, ifdId); + if (idx == -1) return sectionInfo_[unknownTag.sectionId_].desc_; + const TagInfo* tagInfo = tagInfos_[ifdId]; + return sectionInfo_[tagInfo[idx].sectionId_].desc_; + } + if (isMakerIfd(ifdId)) { + const TagInfo* tagInfo = makerTagInfo(tag, ifdId); + if (tagInfo != 0) return sectionInfo_[tagInfo->sectionId_].desc_; + } + return ""; + } // ExifTags::sectionDesc + + uint16_t ExifTags::tag(const std::string& tagName, IfdId ifdId) + { + uint16_t tag = 0xffff; + if (isExifIfd(ifdId)) { + const TagInfo* tagInfo = tagInfos_[ifdId]; + if (tagInfo) { + int idx; + for (idx = 0; tagInfo[idx].tag_ != 0xffff; ++idx) { + if (tagInfo[idx].name_ == tagName) break; + } + tag = tagInfo[idx].tag_; + } + } + if (isMakerIfd(ifdId)) { + const TagInfo* tagInfo = makerTagInfo(tagName, ifdId); + if (tagInfo != 0) tag = tagInfo->tag_; + } + if (tag == 0xffff) { + if (!isHex(tagName, 4, "0x")) throw Error(7, tagName, ifdId); + std::istringstream is(tagName); + is >> std::hex >> tag; + } + return tag; + } // ExifTags::tag + + IfdId ExifTags::ifdIdByIfdItem(const std::string& ifdItem) + { + int i; + for (i = int(lastIfdId) - 1; i > 0; --i) { + if (ifdInfo_[i].item_ == ifdItem) break; + } + return IfdId(i); + } + + const char* ExifTags::ifdName(IfdId ifdId) + { + return ifdInfo_[ifdId].name_; + } + + const char* ExifTags::ifdItem(IfdId ifdId) + { + return ifdInfo_[ifdId].item_; + } + + const char* ExifTags::sectionName(SectionId sectionId) + { + return sectionInfo_[sectionId].name_; + } + + SectionId ExifTags::sectionId(const std::string& sectionName) + { + int i; + for (i = int(lastSectionId) - 1; i > 0; --i) { + if (sectionInfo_[i].name_ == sectionName) break; + } + return SectionId(i); + } + + TypeId ExifTags::tagType(uint16_t tag, IfdId ifdId) + { + if (isExifIfd(ifdId)) { + int idx = tagInfoIdx(tag, ifdId); + if (idx != -1) return tagInfos_[ifdId][idx].typeId_; + } + if (isMakerIfd(ifdId)) { + const TagInfo* tagInfo = makerTagInfo(tag, ifdId); + if (tagInfo != 0) return tagInfo->typeId_; + } + return unknownTag.typeId_; + } + + std::ostream& ExifTags::printTag(std::ostream& os, + uint16_t tag, + IfdId ifdId, + const Value& value) + { + PrintFct fct = printValue; + if (isExifIfd(ifdId)) { + int idx = tagInfoIdx(tag, ifdId); + if (idx != -1) { + fct = tagInfos_[ifdId][idx].printFct_; + } + } + if (isMakerIfd(ifdId)) { + const TagInfo* tagInfo = makerTagInfo(tag, ifdId); + if (tagInfo != 0) fct = tagInfo->printFct_; + } + return fct(os, value); + } // ExifTags::printTag + + void ExifTags::taglist(std::ostream& os) + { + for (int i=0; ifdTagInfo[i].tag_ != 0xffff; ++i) { + os << ifdTagInfo[i] << "\n"; + } + for (int i=0; exifTagInfo[i].tag_ != 0xffff; ++i) { + os << exifTagInfo[i] << "\n"; + } + for (int i=0; iopTagInfo[i].tag_ != 0xffff; ++i) { + os << iopTagInfo[i] << "\n"; + } + for (int i=0; gpsTagInfo[i].tag_ != 0xffff; ++i) { + os << gpsTagInfo[i] << "\n"; + } + } // ExifTags::taglist + + void ExifTags::makerTaglist(std::ostream& os, IfdId ifdId) + { + int i = 0; + for (; i < MAX_MAKER_TAG_INFOS && makerIfdIds_[i] != ifdId; ++i); + if (i != MAX_MAKER_TAG_INFOS) { + const TagInfo* mnTagInfo = makerTagInfos_[i]; + for (int k=0; mnTagInfo[k].tag_ != 0xffff; ++k) { + os << mnTagInfo[k] << "\n"; + } + } + } // ExifTags::makerTaglist + + const char* ExifKey::familyName_ = "Exif"; + + ExifKey::ExifKey(const std::string& key) + : tag_(0), ifdId_(ifdIdNotSet), ifdItem_(""), + idx_(0), key_(key) + { + decomposeKey(); + } + + ExifKey::ExifKey(uint16_t tag, const std::string& ifdItem) + : tag_(0), ifdId_(ifdIdNotSet), ifdItem_(""), + idx_(0), key_("") + { + IfdId ifdId = ExifTags::ifdIdByIfdItem(ifdItem); + if (ExifTags::isMakerIfd(ifdId)) { + MakerNote::AutoPtr makerNote = MakerNoteFactory::create(ifdId); + if (makerNote.get() == 0) throw Error(23, ifdId); + } + tag_ = tag; + ifdId_ = ifdId; + ifdItem_ = ifdItem; + makeKey(); + } + + ExifKey::ExifKey(const Entry& e) + : tag_(e.tag()), ifdId_(e.ifdId()), + ifdItem_(ExifTags::ifdItem(e.ifdId())), + idx_(e.idx()), key_("") + { + makeKey(); + } + + ExifKey::ExifKey(const ExifKey& rhs) + : tag_(rhs.tag_), ifdId_(rhs.ifdId_), ifdItem_(rhs.ifdItem_), + idx_(rhs.idx_), key_(rhs.key_) + { + } + + ExifKey::~ExifKey() + { + } + + ExifKey& ExifKey::operator=(const ExifKey& rhs) + { + if (this == &rhs) return *this; + Key::operator=(rhs); + tag_ = rhs.tag_; + ifdId_ = rhs.ifdId_; + ifdItem_ = rhs.ifdItem_; + idx_ = rhs.idx_; + key_ = rhs.key_; + return *this; + } + + std::string ExifKey::tagName() const + { + return ExifTags::tagName(tag_, ifdId_); + } + + ExifKey::AutoPtr ExifKey::clone() const + { + return AutoPtr(clone_()); + } + + ExifKey* ExifKey::clone_() const + { + return new ExifKey(*this); + } + + std::string ExifKey::sectionName() const + { + return ExifTags::sectionName(tag(), ifdId()); + } + + void ExifKey::decomposeKey() + { + // Get the family name, IFD name and tag 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 ifdItem = key_.substr(pos0, pos1 - pos0); + if (ifdItem == "") throw Error(6, key_); + std::string tagName = key_.substr(pos1 + 1); + if (tagName == "") throw Error(6, key_); + + // Find IfdId + IfdId ifdId = ExifTags::ifdIdByIfdItem(ifdItem); + if (ifdId == ifdIdNotSet) throw Error(6, key_); + if (ExifTags::isMakerIfd(ifdId)) { + MakerNote::AutoPtr makerNote = MakerNoteFactory::create(ifdId); + if (makerNote.get() == 0) throw Error(6, key_); + } + // Convert tag + uint16_t tag = ExifTags::tag(tagName, ifdId); + + // Translate hex tag name (0xabcd) to a real tag name if there is one + tagName = ExifTags::tagName(tag, ifdId); + + tag_ = tag; + ifdId_ = ifdId; + ifdItem_ = ifdItem; + key_ = familyName + "." + ifdItem + "." + tagName; + } + + void ExifKey::makeKey() + { + key_ = std::string(familyName_) + + "." + ifdItem_ + + "." + ExifTags::tagName(tag_, ifdId_); + } + + // ************************************************************************* + // free functions + + bool isExifIfd(IfdId ifdId) + { + bool rc; + switch (ifdId) { + case ifd0Id: rc = true; break; + case exifIfdId: rc = true; break; + case gpsIfdId: rc = true; break; + case iopIfdId: rc = true; break; + case ifd1Id: rc = true; break; + default: rc = false; break; + } + return rc; + } // isExifIfd + + std::ostream& operator<<(std::ostream& os, const TagInfo& ti) + { + ExifKey exifKey(ti.tag_, ExifTags::ifdItem(ti.ifdId_)); + return os << ExifTags::tagName(ti.tag_, ti.ifdId_) << ", " + << std::dec << ti.tag_ << ", " + << "0x" << std::setw(4) << std::setfill('0') + << std::right << std::hex << ti.tag_ << ", " + << ExifTags::ifdName(ti.ifdId_) << ", " + << exifKey.key() << ", " + << TypeInfo::typeName( + ExifTags::tagType(ti.tag_, ti.ifdId_)) << ", " + << ExifTags::tagDesc(ti.tag_, ti.ifdId_); + } + + std::ostream& operator<<(std::ostream& os, const Rational& r) + { + return os << r.first << "/" << r.second; + } + + std::istream& operator>>(std::istream& is, Rational& r) + { + int32_t nominator; + int32_t denominator; + char c; + is >> nominator >> c >> denominator; + if (is && c == '/') r = std::make_pair(nominator, denominator); + return is; + } + + std::ostream& operator<<(std::ostream& os, const URational& r) + { + return os << r.first << "/" << r.second; + } + + std::istream& operator>>(std::istream& is, URational& r) + { + uint32_t nominator; + uint32_t denominator; + char c; + is >> nominator >> c >> denominator; + if (is && c == '/') r = std::make_pair(nominator, denominator); + return is; + } + + std::ostream& printValue(std::ostream& os, const Value& value) + { + return os << value; + } + + std::ostream& printLong(std::ostream& os, const Value& value) + { + return os << value.toLong(); + } + + std::ostream& printFloat(std::ostream& os, const Value& value) + { + Rational r = value.toRational(); + if (r.second != 0) return os << static_cast<float>(r.first) / r.second; + return os << "(" << value << ")"; + } // printFloat + + std::ostream& printUnit(std::ostream& os, const Value& value) + { + long unit = value.toLong(); + switch (unit) { + case 2: os << "inch"; break; + case 3: os << "cm"; break; + default: os << "(" << unit << ")"; break; + } + return os; + } + + std::ostream& print0x0103(std::ostream& os, const Value& value) + { + long compression = value.toLong(); + switch (compression) { + case 1: os << "TIFF"; break; + case 6: os << "JPEG"; break; + default: os << "(" << compression << ")"; break; + } + return os; + } + + std::ostream& print0x0106(std::ostream& os, const Value& value) + { + long photo = value.toLong(); + switch (photo) { + case 2: os << "RGB"; break; + case 6: os << "YCbCr"; break; + default: os << "(" << photo << ")"; break; + } + return os; + } + + std::ostream& print0x0112(std::ostream& os, const Value& value) + { + long orientation = value.toLong(); + switch (orientation) { + case 1: os << "top, left"; break; + case 2: os << "top, right"; break; + case 3: os << "bottom, right"; break; + case 4: os << "bottom, left"; break; + case 5: os << "left, top"; break; + case 6: os << "right, top"; break; + case 7: os << "right, bottom"; break; + case 8: os << "left, bottom"; break; + default: os << "(" << orientation << ")"; break; + } + return os; + } + + std::ostream& print0x0213(std::ostream& os, const Value& value) + { + long position = value.toLong(); + switch (position) { + case 1: os << "Centered"; break; + case 2: os << "Co-sited"; break; + default: os << "(" << position << ")"; break; + } + return os; + } + + std::ostream& print0x8298(std::ostream& os, const Value& value) + { + // Print the copyright information in the format Photographer, Editor + std::string val = value.toString(); + std::string::size_type pos = val.find('\0'); + if (pos != std::string::npos) { + std::string photographer(val, 0, pos); + if (photographer != " ") os << photographer; + std::string editor(val, pos + 1); + if (editor != "") { + if (photographer != " ") os << ", "; + os << editor; + } + } + else { + os << val; + } + return os; + } + + std::ostream& print0x829a(std::ostream& os, const Value& value) + { + Rational t = value.toRational(); + if (t.first > 1 && t.second > 1 && t.second >= t.first) { + t.second = static_cast<uint32_t>( + static_cast<float>(t.second) / t.first + 0.5); + t.first = 1; + } + if (t.second > 1 && t.second < t.first) { + t.first = static_cast<uint32_t>( + static_cast<float>(t.first) / t.second + 0.5); + t.second = 1; + } + if (t.second == 1) { + os << t.first << " s"; + } + else { + os << t.first << "/" << t.second << " s"; + } + return os; + } + + std::ostream& print0x829d(std::ostream& os, const Value& value) + { + Rational fnumber = value.toRational(); + if (fnumber.second != 0) { + os << "F" << (float)fnumber.first / fnumber.second; + } + else { + os << "(" << value << ")"; + } + return os; + } + + std::ostream& print0x8822(std::ostream& os, const Value& value) + { + long program = value.toLong(); + switch (program) { + case 0: os << "Not defined"; break; + case 1: os << "Manual"; break; + case 2: os << "Auto"; break; + case 3: os << "Aperture priority"; break; + case 4: os << "Shutter priority"; break; + case 5: os << "Creative program"; break; + case 6: os << "Action program"; break; + case 7: os << "Portrait mode"; break; + case 8: os << "Landscape mode"; break; + default: os << "(" << program << ")"; break; + } + return os; + } + + std::ostream& print0x8827(std::ostream& os, const Value& value) + { + return os << value.toLong(); + } + + std::ostream& print0x9101(std::ostream& os, const Value& value) + { + for (long i = 0; i < value.count(); ++i) { + long l = value.toLong(i); + switch (l) { + case 0: break; + case 1: os << "Y"; break; + case 2: os << "Cb"; break; + case 3: os << "Cr"; break; + case 4: os << "R"; break; + case 5: os << "G"; break; + case 6: os << "B"; break; + default: os << "(" << l << ")"; break; + } + } + return os; + } + + std::ostream& print0x9204(std::ostream& os, const Value& value) + { + Rational bias = value.toRational(); + if (bias.second <= 0) { + os << "(" << bias.first << "/" << bias.second << ")"; + } + else if (bias.first == 0) { + os << "0"; + } + else { + long d = lgcd(labs(bias.first), bias.second); + long num = labs(bias.first) / d; + long den = bias.second / d; + os << (bias.first < 0 ? "-" : "+") << num; + if (den != 1) { + os << "/" << den; + } + } + return os; + } + + std::ostream& print0x9206(std::ostream& os, const Value& value) + { + Rational distance = value.toRational(); + if (distance.first == 0) { + os << "Unknown"; + } + else if (static_cast<uint32_t>(distance.first) == 0xffffffff) { + os << "Infinity"; + } + else if (distance.second != 0) { + std::ostringstream oss; + oss.copyfmt(os); + os << std::fixed << std::setprecision(2) + << (float)distance.first / distance.second + << " m"; + os.copyfmt(oss); + } + else { + os << "(" << value << ")"; + } + return os; + } + + std::ostream& print0x9207(std::ostream& os, const Value& value) + { + long mode = value.toLong(); + switch (mode) { + case 0: os << "Unknown"; break; + case 1: os << "Average"; break; + case 2: os << "Center weighted"; break; + case 3: os << "Spot"; break; + case 4: os << "Multispot"; break; + case 5: os << "Matrix"; break; + case 6: os << "Partial"; break; + default: os << "(" << mode << ")"; break; + } + return os; + } + + std::ostream& print0x9208(std::ostream& os, const Value& value) + { + long source = value.toLong(); + switch (source) { + case 0: os << "Unknown"; break; + case 1: os << "Daylight"; break; + case 2: os << "Fluorescent"; break; + case 3: os << "Tungsten (incandescent light)"; break; + case 4: os << "Flash"; break; + case 9: os << "Fine weather"; break; + case 10: os << "Cloudy weather"; break; + case 11: os << "Shade"; break; + case 12: os << "Daylight fluorescent (D 5700 - 7100K)"; break; + case 13: os << "Day white fluorescent (N 4600 - 5400K)"; break; + case 14: os << "Cool white fluorescent (W 3900 - 4500K)"; break; + case 15: os << "White fluorescent (WW 3200 - 3700K)"; break; + case 17: os << "Standard light A"; break; + case 18: os << "Standard light B"; break; + case 19: os << "Standard light C"; break; + case 20: os << "D55"; break; + case 21: os << "D65"; break; + case 22: os << "D75"; break; + case 23: os << "D50"; break; + case 24: os << "ISO studio tungsten"; break; + case 255: os << "other light source"; break; + default: os << "(" << source << ")"; break; + } + return os; + } + + std::ostream& print0x9209(std::ostream& os, const Value& value) + { + long flash = value.toLong(); + switch (flash) { + case 0x00: os << "No"; break; + case 0x01: os << "Yes"; break; + case 0x05: os << "Strobe return light not detected"; break; + case 0x07: os << "Strobe return light detected"; break; + case 0x09: os << "Yes, compulsory"; break; + case 0x0d: os << "Yes, compulsory, return light not detected"; break; + case 0x0f: os << "Yes, compulsory, return light detected"; break; + case 0x10: os << "No, compulsory"; break; + case 0x18: os << "No, auto"; break; + case 0x19: os << "Yes, auto"; break; + case 0x1d: os << "Yes, auto, return light not detected"; break; + case 0x1f: os << "Yes, auto, return light detected"; break; + case 0x20: os << "No flash function"; break; + case 0x41: os << "Yes, red-eye reduction"; break; + case 0x45: os << "Yes, red-eye reduction, return light not detected"; break; + case 0x47: os << "Yes, red-eye reduction, return light detected"; break; + case 0x49: os << "Yes, compulsory, red-eye reduction"; break; + case 0x4d: os << "Yes, compulsory, red-eye reduction, return light not detected"; break; + case 0x4f: os << "Yes, compulsory, red-eye reduction, return light detected"; break; + case 0x59: os << "Yes, auto, red-eye reduction"; break; + case 0x5d: os << "Yes, auto, red-eye reduction, return light not detected"; break; + case 0x5f: os << "Yes, auto, red-eye reduction, return light detected"; break; + default: os << "(" << flash << ")"; break; + } + return os; + } + + std::ostream& print0x920a(std::ostream& os, const Value& value) + { + Rational length = value.toRational(); + if (length.second != 0) { + std::ostringstream oss; + oss.copyfmt(os); + os << std::fixed << std::setprecision(1) + << (float)length.first / length.second + << " mm"; + os.copyfmt(oss); + } + else { + os << "(" << value << ")"; + } + return os; + } + + // Todo: Implement this properly + std::ostream& print0x9286(std::ostream& os, const Value& value) + { + if (value.size() > 8) { + DataBuf buf(value.size()); + value.copy(buf.pData_, bigEndian); + // Hack: Skip the leading 8-Byte character code, truncate + // trailing '\0's and let the stream take care of the remainder + std::string userComment(reinterpret_cast<char*>(buf.pData_) + 8, buf.size_ - 8); + std::string::size_type pos = userComment.find_last_not_of('\0'); + os << userComment.substr(0, pos + 1); + } + return os; + } + + std::ostream& print0xa001(std::ostream& os, const Value& value) + { + long space = value.toLong(); + switch (space) { + case 1: os << "sRGB"; break; + case 0xffff: os << "Uncalibrated"; break; + default: os << "(" << space << ")"; break; + } + return os; + } + + std::ostream& print0xa217(std::ostream& os, const Value& value) + { + long method = value.toLong(); + switch (method) { + case 1: os << "Not defined"; break; + case 2: os << "One-chip color area"; break; + case 3: os << "Two-chip color area"; break; + case 4: os << "Three-chip color area"; break; + case 5: os << "Color sequential area"; break; + case 7: os << "Trilinear sensor"; break; + case 8: os << "Color sequential linear"; break; + default: os << "(" << method << ")"; break; + } + return os; + } + + std::ostream& print0xa300(std::ostream& os, const Value& value) + { + long source = value.toLong(); + switch (source) { + case 3: os << "Digital still camera"; break; + default: os << "(" << source << ")"; break; + } + return os; + } + + std::ostream& print0xa301(std::ostream& os, const Value& value) + { + long scene = value.toLong(); + switch (scene) { + case 1: os << "Directly photographed"; break; + default: os << "(" << scene << ")"; break; + } + return os; + } + + std::ostream& print0xa402(std::ostream& os, const Value& value) + { + long mode = value.toLong(); + switch (mode) { + case 0: os << "Auto"; break; + case 1: os << "Manual"; break; + case 2: os << "Auto bracket"; break; + default: os << "(" << mode << ")"; break; + } + return os; + } + + std::ostream& print0xa403(std::ostream& os, const Value& value) + { + long wb = value.toLong(); + switch (wb) { + case 0: os << "Auto"; break; + case 1: os << "Manual"; break; + default: os << "(" << wb << ")"; break; + } + return os; + } + + std::ostream& print0xa404(std::ostream& os, const Value& value) + { + Rational zoom = value.toRational(); + if (zoom.second == 0) { + os << "Digital zoom not used"; + } + else { + std::ostringstream oss; + oss.copyfmt(os); + os << std::fixed << std::setprecision(1) + << (float)zoom.first / zoom.second; + os.copyfmt(oss); + } + return os; + } + + std::ostream& print0xa405(std::ostream& os, const Value& value) + { + long length = value.toLong(); + if (length == 0) { + os << "Unknown"; + } + else { + os << length << ".0 mm"; + } + return os; + } + + std::ostream& print0xa406(std::ostream& os, const Value& value) + { + long scene = value.toLong(); + switch (scene) { + case 0: os << "Standard"; break; + case 1: os << "Landscape"; break; + case 2: os << "Portrait"; break; + case 3: os << "Night scene"; break; + default: os << "(" << scene << ")"; break; + } + return os; + } + + std::ostream& print0xa407(std::ostream& os, const Value& value) + { + long gain = value.toLong(); + switch (gain) { + case 0: os << "None"; break; + case 1: os << "Low gain up"; break; + case 2: os << "High gain up"; break; + case 3: os << "Low gain down"; break; + case 4: os << "High gain down"; break; + default: os << "(" << gain << ")"; break; + } + return os; + } + + std::ostream& print0xa408(std::ostream& os, const Value& value) + { + long contrast = value.toLong(); + switch (contrast) { + case 0: os << "Normal"; break; + case 1: os << "Soft"; break; + case 2: os << "Hard"; break; + default: os << "(" << contrast << ")"; break; + } + return os; + } + + std::ostream& print0xa409(std::ostream& os, const Value& value) + { + long saturation = value.toLong(); + switch (saturation) { + case 0: os << "Normal"; break; + case 1: os << "Low"; break; + case 2: os << "High"; break; + default: os << "(" << saturation << ")"; break; + } + return os; + } + + std::ostream& print0xa40a(std::ostream& os, const Value& value) + { + long sharpness = value.toLong(); + switch (sharpness) { + case 0: os << "Normal"; break; + case 1: os << "Soft"; break; + case 2: os << "Hard"; break; + default: os << "(" << sharpness << ")"; break; + } + return os; + } + + std::ostream& print0xa40c(std::ostream& os, const Value& value) + { + long distance = value.toLong(); + switch (distance) { + case 0: os << "Unknown"; break; + case 1: os << "Macro"; break; + case 2: os << "Close view"; break; + case 3: os << "Distant view"; break; + default: os << "(" << distance << ")"; break; + } + return os; + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/tags.hpp b/src/plugins/exiv2/tags.hpp @@ -0,0 +1,444 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file tags.hpp + @brief Exif tag and type information + @version $Rev: 580 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 15-Jan-04, ahu: created<BR> + 11-Feb-04, ahu: isolated as a component + */ +#ifndef TAGS_HPP_ +#define TAGS_HPP_ + +// ***************************************************************************** +// included header files +#include "metadatum.hpp" +#include "types.hpp" + +// + standard includes +#include <string> +#include <utility> // for std::pair +#include <iosfwd> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class declarations + class Value; + class Entry; + +// ***************************************************************************** +// type definitions + + //! Type for a function pointer for functions interpreting the tag value + typedef std::ostream& (*PrintFct)(std::ostream&, const Value&); + + /*! + @brief Section identifiers to logically group tags. A section consists + of nothing more than a name, based on the Exif standard. + */ + enum SectionId { sectionIdNotSet, + imgStruct, recOffset, imgCharacter, otherTags, exifFormat, + exifVersion, imgConfig, userInfo, relatedFile, dateTime, + captureCond, gpsTags, iopTags, makerTags, + lastSectionId }; + +// ***************************************************************************** +// class definitions + + //! Contains information pertaining to one IFD + struct IfdInfo { + //! Constructor + IfdInfo(IfdId ifdId, const char* name, const char* item); + IfdId ifdId_; //!< IFD id + const char* name_; //!< IFD name + //! Related IFD item. This is also an IFD name, unique for each IFD. + const char* item_; + }; + + //! Contains information pertaining to one section + struct SectionInfo { + //! Constructor + SectionInfo(SectionId sectionId, const char* name, const char* desc); + SectionId sectionId_; //!< Section id + const char* name_; //!< Section name (one word) + const char* desc_; //!< Section description + }; + + //! Tag information + struct TagInfo { + //! Constructor + TagInfo( + uint16_t tag, + const char* name, + const char* desc, + IfdId ifdId, + SectionId sectionId, + TypeId typeId, + PrintFct printFct + ); + uint16_t tag_; //!< Tag + const char* name_; //!< One word tag label + const char* desc_; //!< Short tag description + IfdId ifdId_; //!< Link to the (prefered) IFD + SectionId sectionId_; //!< Section id + TypeId typeId_; //!< Type id + PrintFct printFct_; //!< Pointer to tag print function + }; // struct TagInfo + + /*! + @brief Helper structure for lookup tables for translations of numeric + tag values to human readable labels. + */ + struct TagDetails { + long val_; //!< Tag value + char* label_; //!< Translation of the tag value + }; // struct TagDetails + + /*! + @brief Translation from numeric values from a lookup list to human + readable labels + */ + class TagTranslator { + public: + //! @name Creators + //@{ + //! Default constructor. + explicit TagTranslator(const TagDetails* pTagDetails) + : pTagDetails_(pTagDetails) {} + // No d'tor: Do not delete the list. + //@} + + //! @name Accessors + //@{ + //! Translate the tag value and write it out to the provided stream + std::ostream& print(std::ostream& os, const Value& value) const; + //@} + private: + const TagDetails* pTagDetails_; + }; // class TagTranslator + + //! Container for Exif tag information. Implemented as a static class. + class ExifTags { + //! Prevent construction: not implemented. + ExifTags() {} + //! Prevent copy-construction: not implemented. + ExifTags(const ExifTags& rhs); + //! Prevent assignment: not implemented. + ExifTags& operator=(const ExifTags& rhs); + + public: + /*! + @brief Return the name of the tag or a string with the hexadecimal + value of the tag in the form "0x01ff", if the tag is not + a known Exif tag. + + @param tag The tag + @param ifdId IFD id + @return The name of the tag or a string containing the hexadecimal + value of the tag in the form "0x01ff", if this is an unknown + tag. + */ + static std::string tagName(uint16_t tag, IfdId ifdId); + /*! + @brief Return the description of the tag. + @param tag The tag + @param ifdId IFD id + @return The description of the tag or a string indicating that + the tag is unknown. + */ + static const char* tagDesc(uint16_t tag, IfdId ifdId); + /*! + @brief Return the tag for one combination of IFD id and tagName. + If the tagName is not known, it expects tag names in the + form "0x01ff" and converts them to unsigned integer. + + @throw Error if the tagname or ifdId is invalid + */ + static uint16_t tag(const std::string& tagName, IfdId ifdId); + //! Return the IFD id for an IFD item + static IfdId ifdIdByIfdItem(const std::string& ifdItem); + //! Return the name of the IFD + static const char* ifdName(IfdId ifdId); + //! Return the related image item (image or thumbnail) + static const char* ifdItem(IfdId ifdId); + //! Return the name of the section + static const char* sectionName(SectionId sectionId); + /*! + @brief Return the name of the section for a combination of + tag and IFD id. + @param tag The tag + @param ifdId IFD id + @return The name of the section or a string indicating that the + section or the tag is unknown. + */ + static const char* sectionName(uint16_t tag, IfdId ifdId); + /*! + @brief Return the description of the section for a combination of + tag and IFD id. + @param tag The tag + @param ifdId IFD id + @return The description of the section or a string indicating that + the section or the tag is unknown. + */ + static const char* sectionDesc(uint16_t tag, IfdId ifdId); + //! Return the section id for a section name + static SectionId sectionId(const std::string& sectionName); + //! Return the type for tag and IFD id + static TypeId tagType(uint16_t tag, IfdId ifdId); + //! Interpret and print the value of an Exif tag + static std::ostream& printTag(std::ostream& os, + uint16_t tag, + IfdId ifdId, + const Value& value); + //! Print a list of all standard Exif tags to output stream + static void taglist(std::ostream& os); + //! Print a list of all tags related to one makernote %IfdId + static void makerTaglist(std::ostream& os, IfdId ifdId); + //! Register an %IfdId with the base IFD %TagInfo list for a makernote + static void registerBaseTagInfo(IfdId ifdId); + /*! + @brief Register an %IfdId and %TagInfo list for a makernote + + @throw Error if the MakerTagInfo registry is full + */ + static void registerMakerTagInfo(IfdId ifdId, const TagInfo* tagInfo); + /*! + @brief Return true if \em ifdId is an %Ifd Id which is registered + as a makernote %Ifd id. Note: Calling this function with + makerIfd returns false. + */ + static bool isMakerIfd(IfdId ifdId); + + private: + static int tagInfoIdx(uint16_t tag, IfdId ifdId); + static const TagInfo* makerTagInfo(uint16_t tag, IfdId ifdId); + static const TagInfo* makerTagInfo(const std::string& tagName, + IfdId ifdId); + + static const IfdInfo ifdInfo_[]; + static const SectionInfo sectionInfo_[]; + + static const TagInfo* tagInfos_[]; + + static const int MAX_MAKER_TAG_INFOS = 64; + static const TagInfo* makerTagInfos_[MAX_MAKER_TAG_INFOS]; + static IfdId makerIfdIds_[MAX_MAKER_TAG_INFOS]; + + }; // class ExifTags + + /*! + @brief Concrete keys for Exif metadata. + */ + class ExifKey : public Key { + public: + //! Shortcut for an %ExifKey auto pointer. + typedef std::auto_ptr<ExifKey> AutoPtr; + + //! @name Creators + //@{ + /*! + @brief Constructor to create an Exif key from a key string. + + @param key The key string. + @throw Error if the first part of the key is not '<b>Exif</b>' or + the remainin parts of the key cannot be parsed and + converted to an ifd-item and tag name. + */ + explicit ExifKey(const std::string& key); + /*! + @brief Constructor to create an Exif key from a tag and IFD item + string. + @param tag The tag value + @param ifdItem The IFD string. For MakerNote tags, this must be the + IFD item of the specific MakerNote. "MakerNote" is not allowed. + @throw Error if the key cannot be constructed from the tag and IFD + item parameters. + */ + ExifKey(uint16_t tag, const std::string& ifdItem); + //! Constructor to build an ExifKey from an IFD entry. + explicit ExifKey(const Entry& e); + //! Copy constructor + ExifKey(const ExifKey& rhs); + virtual ~ExifKey(); + //@} + + //! @name Manipulators + //@{ + /*! + @brief Assignment operator. + */ + ExifKey& operator=(const ExifKey& rhs); + //@} + + //! @name Accessors + //@{ + virtual std::string key() const { return key_; } + virtual const char* familyName() const { return familyName_; } + /*! + @brief Return the name of the group (the second part of the key). + For Exif keys, the group name is the IFD item. + */ + virtual std::string groupName() const { return ifdItem(); } + virtual std::string tagName() const; + virtual uint16_t tag() const { return tag_; } + + AutoPtr clone() const; + //! Return the IFD id + IfdId ifdId() const { return ifdId_; } + //! Return the name of the IFD + const char* ifdName() const { return ExifTags::ifdName(ifdId()); } + //! Return the related image item + std::string ifdItem() const { return ifdItem_; } + //! Return the name of the Exif section (deprecated) + std::string sectionName() const; + //! Return the index (unique id of this key within the original IFD) + int idx() const { return idx_; } + //@} + + protected: + //! @name Manipulators + //@{ + /*! + @brief Set the key corresponding to the tag and IFD id. + The key is of the form '<b>Exif</b>.ifdItem.tagName'. + */ + void makeKey(); + /*! + @brief Parse and convert the key string into tag and IFD Id. + Updates data members if the string can be decomposed, + or throws \em Error . + + @throw Error if the key cannot be decomposed. + */ + void decomposeKey(); + //@} + + private: + //! Internal virtual copy constructor. + virtual ExifKey* clone_() const; + + // DATA + static const char* familyName_; + + uint16_t tag_; //!< Tag value + IfdId ifdId_; //!< The IFD associated with this tag + std::string ifdItem_; //!< The IFD item + int idx_; //!< Unique id of an entry within one IFD + std::string key_; //!< Key + }; // class ExifKey + +// ***************************************************************************** +// free functions + + /*! + @brief Return true if \em ifdId is an Exif %Ifd Id, i.e., one of + ifd0Id, exifIfdId, gpsIfdId, iopIfdId or ifd1Id, else false. + This is used to differentiate between standard Exif %Ifds + and %Ifds associated with the makernote. + */ + bool isExifIfd(IfdId ifdId); + + //! Output operator for TagInfo + std::ostream& operator<<(std::ostream& os, const TagInfo& ti); + + //! @name Functions printing interpreted tag values + //@{ + //! Default print function, using the Value output operator + std::ostream& printValue(std::ostream& os, const Value& value); + //! Print the value converted to a long + std::ostream& printLong(std::ostream& os, const Value& value); + //! Print a Rational or URational value in floating point format + std::ostream& printFloat(std::ostream& os, const Value& value); + //! Print the unit for measuring X and Y resolution + std::ostream& printUnit(std::ostream& os, const Value& value); + + //! Print the compression scheme used for the image data + std::ostream& print0x0103(std::ostream& os, const Value& value); + //! Print the pixel composition + std::ostream& print0x0106(std::ostream& os, const Value& value); + //! Print the orientation + std::ostream& print0x0112(std::ostream& os, const Value& value); + //! Print the YCbCrPositioning + std::ostream& print0x0213(std::ostream& os, const Value& value); + //! Print the Copyright + std::ostream& print0x8298(std::ostream& os, const Value& value); + //! Print the Exposure time + std::ostream& print0x829a(std::ostream& os, const Value& value); + //! Print the F number + std::ostream& print0x829d(std::ostream& os, const Value& value); + //! Print the Exposure mode + std::ostream& print0x8822(std::ostream& os, const Value& value); + //! Print ISO speed ratings + std::ostream& print0x8827(std::ostream& os, const Value& value); + //! Print components configuration specific to compressed data + std::ostream& print0x9101(std::ostream& os, const Value& value); + //! Print the exposure bias value + std::ostream& print0x9204(std::ostream& os, const Value& value); + //! Print the subject distance + std::ostream& print0x9206(std::ostream& os, const Value& value); + //! Print the metering mode + std::ostream& print0x9207(std::ostream& os, const Value& value); + //! Print the light source + std::ostream& print0x9208(std::ostream& os, const Value& value); + //! Print the flash status + std::ostream& print0x9209(std::ostream& os, const Value& value); + //! Print the actual focal length of the lens + std::ostream& print0x920a(std::ostream& os, const Value& value); + //! Print the user comment + std::ostream& print0x9286(std::ostream& os, const Value& value); + //! Print color space information + std::ostream& print0xa001(std::ostream& os, const Value& value); + //! Print info on image sensor type on the camera or input device + std::ostream& print0xa217(std::ostream& os, const Value& value); + //! Print file source + std::ostream& print0xa300(std::ostream& os, const Value& value); + //! Print scene type + std::ostream& print0xa301(std::ostream& os, const Value& value); + //! Print the exposure mode + std::ostream& print0xa402(std::ostream& os, const Value& value); + //! Print white balance information + std::ostream& print0xa403(std::ostream& os, const Value& value); + //! Print digital zoom ratio + std::ostream& print0xa404(std::ostream& os, const Value& value); + //! Print 35mm equivalent focal length + std::ostream& print0xa405(std::ostream& os, const Value& value); + //! Print scene capture type + std::ostream& print0xa406(std::ostream& os, const Value& value); + //! Print overall image gain adjustment + std::ostream& print0xa407(std::ostream& os, const Value& value); + //! Print contract adjustment + std::ostream& print0xa408(std::ostream& os, const Value& value); + //! Print saturation adjustment + std::ostream& print0xa409(std::ostream& os, const Value& value); + //! Print sharpness adjustment + std::ostream& print0xa40a(std::ostream& os, const Value& value); + //! Print subject distance range + std::ostream& print0xa40c(std::ostream& os, const Value& value); + //@} +} // namespace Exiv2 + +#endif // #ifndef TAGS_HPP_ diff --git a/src/plugins/exiv2/types.cpp b/src/plugins/exiv2/types.cpp @@ -0,0 +1,343 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: types.cpp + Version: $Rev: 578 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 26-Jan-04, ahu: created + 11-Feb-04, ahu: isolated as a component + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: types.cpp 578 2005-06-07 15:01:11Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "types.hpp" + +// + standard includes +#include <string> +#include <iostream> +#include <iomanip> +#include <sstream> +#include <utility> +#include <cctype> + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + TypeInfoTable::TypeInfoTable(TypeId typeId, const char* name, long size) + : typeId_(typeId), name_(name), size_(size) + { + } + + //! Lookup list of supported IFD type information + const TypeInfoTable TypeInfo::typeInfoTable_[] = { + TypeInfoTable(invalidTypeId, "Invalid", 0), + TypeInfoTable(unsignedByte, "Byte", 1), + TypeInfoTable(asciiString, "Ascii", 1), + TypeInfoTable(unsignedShort, "Short", 2), + TypeInfoTable(unsignedLong, "Long", 4), + TypeInfoTable(unsignedRational, "Rational", 8), + TypeInfoTable(invalid6, "Invalid(6)", 1), + TypeInfoTable(undefined, "Undefined", 1), + TypeInfoTable(signedShort, "SShort", 2), + TypeInfoTable(signedLong, "SLong", 4), + TypeInfoTable(signedRational, "SRational", 8), + TypeInfoTable(string, "String", 1), + TypeInfoTable(date, "Date", 8), + TypeInfoTable(time, "Time", 11), + TypeInfoTable(comment, "Comment", 1), + // End of list marker + TypeInfoTable(lastTypeId, "(Unknown)", 0) + }; + + const char* TypeInfo::typeName(TypeId typeId) + { + return typeInfoTable_[ typeId < lastTypeId ? typeId : 0 ].name_; + } + + TypeId TypeInfo::typeId(const std::string& typeName) + { + int i = 0; + for (; typeInfoTable_[i].typeId_ != lastTypeId + && typeInfoTable_[i].name_ != typeName; ++i) {} + return typeInfoTable_[i].typeId_ == lastTypeId ? + invalidTypeId : typeInfoTable_[i].typeId_; + } + + long TypeInfo::typeSize(TypeId typeId) + { + return typeInfoTable_[ typeId < lastTypeId ? typeId : 0 ].size_; + } + + DataBuf::DataBuf(DataBuf& rhs) + : pData_(rhs.pData_), size_(rhs.size_) + { + rhs.release(); + } + + DataBuf::DataBuf(byte* pData, long size) + : pData_(0), size_(0) + { + if (size > 0) { + pData_ = new byte[size]; + memcpy(pData_, pData, size); + size_ = size; + } + } + + DataBuf& DataBuf::operator=(DataBuf& rhs) + { + if (this == &rhs) return *this; + reset(rhs.release()); + return *this; + } + + void DataBuf::alloc(long size) + { + if (size > size_) { + delete[] pData_; + size_ = size; + pData_ = new byte[size]; + } + } + + std::pair<byte*, long> DataBuf::release() + { + std::pair<byte*, long> p = std::make_pair(pData_, size_); + pData_ = 0; + size_ = 0; + return p; + } + + void DataBuf::reset(std::pair<byte*, long> p) + { + if (pData_ != p.first) { + delete[] pData_; + pData_ = p.first; + } + size_ = p.second; + } + + // ************************************************************************* + // free functions + + uint16_t getUShort(const byte* buf, ByteOrder byteOrder) + { + if (byteOrder == littleEndian) { + return (byte)buf[1] << 8 | (byte)buf[0]; + } + else { + return (byte)buf[0] << 8 | (byte)buf[1]; + } + } + + uint32_t getULong(const byte* buf, ByteOrder byteOrder) + { + if (byteOrder == littleEndian) { + return (byte)buf[3] << 24 | (byte)buf[2] << 16 + | (byte)buf[1] << 8 | (byte)buf[0]; + } + else { + return (byte)buf[0] << 24 | (byte)buf[1] << 16 + | (byte)buf[2] << 8 | (byte)buf[3]; + } + } + + URational getURational(const byte* buf, ByteOrder byteOrder) + { + uint32_t nominator = getULong(buf, byteOrder); + uint32_t denominator = getULong(buf + 4, byteOrder); + return std::make_pair(nominator, denominator); + } + + int16_t getShort(const byte* buf, ByteOrder byteOrder) + { + if (byteOrder == littleEndian) { + return (byte)buf[1] << 8 | (byte)buf[0]; + } + else { + return (byte)buf[0] << 8 | (byte)buf[1]; + } + } + + int32_t getLong(const byte* buf, ByteOrder byteOrder) + { + if (byteOrder == littleEndian) { + return (byte)buf[3] << 24 | (byte)buf[2] << 16 + | (byte)buf[1] << 8 | (byte)buf[0]; + } + else { + return (byte)buf[0] << 24 | (byte)buf[1] << 16 + | (byte)buf[2] << 8 | (byte)buf[3]; + } + } + + Rational getRational(const byte* buf, ByteOrder byteOrder) + { + int32_t nominator = getLong(buf, byteOrder); + int32_t denominator = getLong(buf + 4, byteOrder); + return std::make_pair(nominator, denominator); + } + + long us2Data(byte* buf, uint16_t s, ByteOrder byteOrder) + { + if (byteOrder == littleEndian) { + buf[0] = (byte)(s & 0x00ff); + buf[1] = (byte)((s & 0xff00) >> 8); + } + else { + buf[0] = (byte)((s & 0xff00) >> 8); + buf[1] = (byte)(s & 0x00ff); + } + return 2; + } + + long ul2Data(byte* buf, uint32_t l, ByteOrder byteOrder) + { + if (byteOrder == littleEndian) { + buf[0] = (byte)(l & 0x000000ff); + buf[1] = (byte)((l & 0x0000ff00) >> 8); + buf[2] = (byte)((l & 0x00ff0000) >> 16); + buf[3] = (byte)((l & 0xff000000) >> 24); + } + else { + buf[0] = (byte)((l & 0xff000000) >> 24); + buf[1] = (byte)((l & 0x00ff0000) >> 16); + buf[2] = (byte)((l & 0x0000ff00) >> 8); + buf[3] = (byte)(l & 0x000000ff); + } + return 4; + } + + long ur2Data(byte* buf, URational l, ByteOrder byteOrder) + { + long o = ul2Data(buf, l.first, byteOrder); + o += ul2Data(buf+o, l.second, byteOrder); + return o; + } + + long s2Data(byte* buf, int16_t s, ByteOrder byteOrder) + { + if (byteOrder == littleEndian) { + buf[0] = (byte)(s & 0x00ff); + buf[1] = (byte)((s & 0xff00) >> 8); + } + else { + buf[0] = (byte)((s & 0xff00) >> 8); + buf[1] = (byte)(s & 0x00ff); + } + return 2; + } + + long l2Data(byte* buf, int32_t l, ByteOrder byteOrder) + { + if (byteOrder == littleEndian) { + buf[0] = (byte)(l & 0x000000ff); + buf[1] = (byte)((l & 0x0000ff00) >> 8); + buf[2] = (byte)((l & 0x00ff0000) >> 16); + buf[3] = (byte)((l & 0xff000000) >> 24); + } + else { + buf[0] = (byte)((l & 0xff000000) >> 24); + buf[1] = (byte)((l & 0x00ff0000) >> 16); + buf[2] = (byte)((l & 0x0000ff00) >> 8); + buf[3] = (byte)(l & 0x000000ff); + } + return 4; + } + + long r2Data(byte* buf, Rational l, ByteOrder byteOrder) + { + long o = l2Data(buf, l.first, byteOrder); + o += l2Data(buf+o, l.second, byteOrder); + return o; + } + + void hexdump(std::ostream& os, const byte* buf, long len, long offset) + { + const std::string::size_type pos = 8 + 16 * 3 + 2; + const std::string align(pos, ' '); + + long i = 0; + while (i < len) { + os << " " + << std::setw(4) << std::setfill('0') << std::hex + << i + offset << " "; + std::ostringstream ss; + do { + byte c = buf[i]; + os << std::setw(2) << std::setfill('0') << std::right + << std::hex << (int)c << " "; + ss << ((int)c >= 31 && (int)c < 127 ? char(buf[i]) : '.'); + } while (++i < len && i%16 != 0); + std::string::size_type width = 9 + ((i-1)%16 + 1) * 3; + os << (width > pos ? "" : align.substr(width)) << ss.str() << "\n"; + } + os << std::dec << std::setfill(' '); + } // hexdump + + int gcd(int a, int b) + { + int temp; + if (a < b) { + temp = a; + a = b; + b = temp; + } + while ((temp = a % b) != 0) { + a = b; + b = temp; + } + return b; + } // gcd + + long lgcd(long a, long b) + { + long temp; + if (a < b) { + temp = a; + a = b; + b = temp; + } + while ((temp = a % b) != 0) { + a = b; + b = temp; + } + return b; + } // lgcd + + bool isHex(const std::string& str, size_t size, const std::string& prefix) + { + if ( str.size() <= prefix.size() + || str.substr(0, prefix.size()) != prefix) return false; + if ( size > 0 + && str.size() != size + prefix.size()) return false; + + for (size_t i = prefix.size(); i < str.size(); ++i) { + if (!isxdigit(str[i])) return false; + } + return true; + } // isHex + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/types.hpp b/src/plugins/exiv2/types.hpp @@ -0,0 +1,307 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file types.hpp + @brief Type definitions for %Exiv2 and related functionality + @version $Rev: 581 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 09-Jan-04, ahu: created<BR> + 11-Feb-04, ahu: isolated as a component + 31-Jul-04, brad: added Time, Data and String values + */ +#ifndef TYPES_HPP_ +#define TYPES_HPP_ + +// ***************************************************************************** +// included header files +#ifdef _MSC_VER +# include "exv_msvc.h" +#else +# include "exv_conf.h" +#endif + +// + standard includes +#include <string> +#include <iosfwd> +#include <utility> +#include <sstream> +#include <cstdio> +#ifdef EXV_HAVE_STDINT_H +# include <stdint.h> +#endif + +// MSVC doesn't provide C99 types, but it has MS specific variants +#ifdef _MSC_VER +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef __int16 int16_t; +typedef __int32 int32_t; +#endif + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// type definitions + + //! 1 byte unsigned integer type. + typedef uint8_t byte; + + //! 8 byte unsigned rational type. + typedef std::pair<uint32_t, uint32_t> URational; + //! 8 byte signed rational type. + typedef std::pair<int32_t, int32_t> Rational; + + //! Type to express the byte order (little or big endian) + enum ByteOrder { invalidByteOrder, littleEndian, bigEndian }; + + //! Type identifiers for IFD format types + enum TypeId { invalidTypeId, unsignedByte, asciiString, unsignedShort, + unsignedLong, unsignedRational, invalid6, undefined, + signedShort, signedLong, signedRational, + string, date, time, + comment, + lastTypeId }; + + // Todo: decentralize IfdId, so that new ids can be defined elsewhere + //! Type to specify the IFD to which a metadata belongs + enum IfdId { ifdIdNotSet, + ifd0Id, exifIfdId, gpsIfdId, iopIfdId, ifd1Id, + canonIfdId, canonCs1IfdId, canonCs2IfdId, canonCfIfdId, + fujiIfdId, nikon1IfdId, nikon2IfdId, nikon3IfdId, + olympusIfdId, panasonicIfdId, sigmaIfdId, sonyIfdId, + lastIfdId }; + +// ***************************************************************************** +// class definitions + + //! Information pertaining to the defined types + struct TypeInfoTable { + //! Constructor + TypeInfoTable(TypeId typeId, const char* name, long size); + TypeId typeId_; //!< Type id + const char* name_; //!< Name of the type + long size_; //!< Bytes per data entry + }; // struct TypeInfoTable + + //! Type information lookup functions. Implemented as a static class. + class TypeInfo { + //! Prevent construction: not implemented. + TypeInfo() {} + //! Prevent copy-construction: not implemented. + TypeInfo(const TypeInfo& rhs); + //! Prevent assignment: not implemented. + TypeInfo& operator=(const TypeInfo& rhs); + + public: + //! Return the name of the type + static const char* typeName(TypeId typeId); + //! Return the type id for a type name + static TypeId typeId(const std::string& typeName); + //! Return the size in bytes of one element of this type + static long typeSize(TypeId typeId); + + private: + static const TypeInfoTable typeInfoTable_[]; + }; + + /*! + @brief Auxiliary type to enable copies and assignments, similar to + std::auto_ptr_ref. See http://www.josuttis.com/libbook/auto_ptr.html + for a discussion. + */ + struct DataBufRef { + //! Constructor + DataBufRef(std::pair<byte*, long> rhs) : p(rhs) {} + //! Pointer to a byte array and its size + std::pair<byte*, long> p; + }; + + /*! + @brief Utility class containing a character array. All it does is to take + care of memory allocation and deletion. Its primary use is meant to + be as a stack variable in functions that need a temporary data + buffer. Todo: this should be some sort of smart pointer, + essentially an std::auto_ptr for a character array. But it isn't... + */ + class DataBuf { + public: + //! @name Creators + //@{ + //! Default constructor + DataBuf() : pData_(0), size_(0) {} + //! Constructor with an initial buffer size + explicit DataBuf(long size) : pData_(new byte[size]), size_(size) {} + //! Constructor, copies an existing buffer + DataBuf(byte* pData, long size); + /*! + @brief Copy constructor. Transfers the buffer to the newly created + object similar to std::auto_ptr, i.e., the original object is + modified. + */ + DataBuf(DataBuf& rhs); + //! Destructor, deletes the allocated buffer + ~DataBuf() { delete[] pData_; } + //@} + + //! @name Manipulators + //@{ + /*! + @brief Assignment operator. Transfers the buffer and releases the + buffer at the original object similar to std::auto_ptr, i.e., + the original object is modified. + */ + DataBuf& operator=(DataBuf& rhs); + //! Allocate a data buffer of the given size + void alloc(long size); + /*! + @brief Release ownership of the buffer to the caller. Returns the + buffer as a data pointer and size pair, resets the internal + buffer. + */ + std::pair<byte*, long> release(); + //! Reset value + void reset(std::pair<byte*, long> =std::make_pair(0,0)); + //@} + + /*! + @name Conversions + + Special conversions with auxiliary type to enable copies + and assignments, similar to those used for std::auto_ptr. + See http://www.josuttis.com/libbook/auto_ptr.html for a discussion. + */ + //@{ + DataBuf(DataBufRef rhs) : pData_(rhs.p.first), size_(rhs.p.second) {} + DataBuf& operator=(DataBufRef rhs) { reset(rhs.p); return *this; } + operator DataBufRef() { return DataBufRef(release()); } + //@} + + // DATA + //! Pointer to the buffer, 0 if none has been allocated + byte* pData_; + //! The current size of the buffer + long size_; + }; // class DataBuf + + +// ***************************************************************************** +// free functions + + //! Read a 2 byte unsigned short value from the data buffer + uint16_t getUShort(const byte* buf, ByteOrder byteOrder); + //! Read a 4 byte unsigned long value from the data buffer + uint32_t getULong(const byte* buf, ByteOrder byteOrder); + //! Read an 8 byte unsigned rational value from the data buffer + URational getURational(const byte* buf, ByteOrder byteOrder); + //! Read a 2 byte signed short value from the data buffer + int16_t getShort(const byte* buf, ByteOrder byteOrder); + //! Read a 4 byte signed long value from the data buffer + int32_t getLong(const byte* buf, ByteOrder byteOrder); + //! Read an 8 byte signed rational value from the data buffer + Rational getRational(const byte* buf, ByteOrder byteOrder); + + //! Output operator for our fake rational + std::ostream& operator<<(std::ostream& os, const Rational& r); + //! Input operator for our fake rational + std::istream& operator>>(std::istream& is, Rational& r); + //! Output operator for our fake unsigned rational + std::ostream& operator<<(std::ostream& os, const URational& r); + //! Input operator for our fake unsigned rational + std::istream& operator>>(std::istream& is, URational& r); + + /*! + @brief Convert an unsigned short to data, write the data to the buffer, + return number of bytes written. + */ + long us2Data(byte* buf, uint16_t s, ByteOrder byteOrder); + /*! + @brief Convert an unsigned long to data, write the data to the buffer, + return number of bytes written. + */ + long ul2Data(byte* buf, uint32_t l, ByteOrder byteOrder); + /*! + @brief Convert an unsigned rational to data, write the data to the buffer, + return number of bytes written. + */ + long ur2Data(byte* buf, URational l, ByteOrder byteOrder); + /*! + @brief Convert a signed short to data, write the data to the buffer, + return number of bytes written. + */ + long s2Data(byte* buf, int16_t s, ByteOrder byteOrder); + /*! + @brief Convert a signed long to data, write the data to the buffer, + return number of bytes written. + */ + long l2Data(byte* buf, int32_t l, ByteOrder byteOrder); + /*! + @brief Convert a signed rational to data, write the data to the buffer, + return number of bytes written. + */ + long r2Data(byte* buf, Rational l, ByteOrder byteOrder); + + /*! + @brief Print len bytes from buf in hex and ASCII format to the given + stream, prefixed with the position in the buffer adjusted by + offset. + */ + void hexdump(std::ostream& os, const byte* buf, long len, long offset =0); + + /*! + @brief Return the greatest common denominator of integers a and b. + Both parameters must be greater than 0. + */ + int gcd(int a, int b); + + /*! + @brief Return the greatest common denominator of long values a and b. + Both parameters must be greater than 0. + */ + long lgcd(long a, long b); + + /*! + @brief Return true if str is a hex number starting with prefix followed + by size hex digits, false otherwise. If size is 0, any number of + digits is allowed and all are checked. + */ + bool isHex(const std::string& str, + size_t size =0, + const std::string& prefix =""); + +// ***************************************************************************** +// template and inline definitions + + //! Utility function to convert the argument of any type to a string + template<typename T> + std::string toString(const T& arg) + { + std::ostringstream os; + os << arg; + return os.str(); + } + +} // namespace Exiv2 + +#endif // #ifndef TYPES_HPP_ diff --git a/src/plugins/exiv2/utils.cpp b/src/plugins/exiv2/utils.cpp @@ -0,0 +1,141 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: utils.cpp + Version: $Rev: 560 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 08-Dec-03, ahu: created + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: utils.cpp 560 2005-04-17 11:51:32Z ahuggel $"); + +// ***************************************************************************** +// included header files +#ifdef _MSC_VER +# include "exv_msvc.h" +#else +# include "exv_conf.h" +#endif + +#include "utils.hpp" + +// + standard includes +#include <sys/types.h> +#include <sys/stat.h> +#ifdef _MSC_VER +# include "getopt_win32.h" +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif +#ifdef EXV_HAVE_UNISTD_H +# include <unistd.h> // for getopt(), stat() +#endif + +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <string> +#include <iostream> +#include <sstream> + +namespace Util { + +// ***************************************************************************** +// class Getopt +int Getopt::getopt(int argc, char* const argv[], const std::string& optstring) +{ + progname_ = Util::basename(argv[0]); + + for (;;) { + int c = ::getopt(argc, argv, optstring.c_str()); + if (c == -1) break; + errcnt_ += option(c, ::optarg == 0 ? "" : ::optarg, ::optopt); + } + for (int i = ::optind; i < argc; i++) { + errcnt_ += nonoption(argv[i]); + } + return errcnt_; +} + +// ***************************************************************************** +// free functions + + std::string dirname(const std::string& path) + { + if (path == "") return "."; + // Strip trailing slashes or backslashes + std::string p = path; + while ( p.length() > 1 + && (p[p.length()-1] == '\\' || p[p.length()-1] == '/')) { + p = p.substr(0, p.length()-1); + } + if (p == "\\" || p == "/") return p; + if (p.length() == 2 && p[1] == ':') return p; // For Windows paths + std::string::size_type idx = p.find_last_of("\\/"); + if (idx == std::string::npos) return "."; + if (idx == 1 && p[0] == '\\' && p[1] == '\\') return p; // For Windows paths + p = p.substr(0, idx == 0 ? 1 : idx); + while ( p.length() > 1 + && (p[p.length()-1] == '\\' || p[p.length()-1] == '/')) { + p = p.substr(0, p.length()-1); + } + return p; + } + + std::string basename(const std::string& path, bool delsuffix) + { + if (path == "") return "."; + // Strip trailing slashes or backslashes + std::string p = path; + while ( p.length() > 1 + && (p[p.length()-1] == '\\' || p[p.length()-1] == '/')) { + p = p.substr(0, p.length()-1); + } + if (p.length() == 2 && p[1] == ':') return ""; // For Windows paths + std::string::size_type idx = p.find_last_of("\\/"); + if (idx == 1 && p[0] == '\\' && p[1] == '\\') return ""; // For Windows paths + if (idx != std::string::npos) p = p.substr(idx+1); + if (delsuffix) p = p.substr(0, p.length() - suffix(p).length()); + return p; + } + + std::string suffix(const std::string& path) + { + std::string b = basename(path); + std::string::size_type idx = b.rfind('.'); + if (idx == std::string::npos || idx == 0 || idx == b.length()-1) { + return ""; + } + return b.substr(idx); + } + + bool strtol(const char* nptr, long& n) + { + if (!nptr || *nptr == '\0') return false; + char* endptr = 0; + long tmp = ::strtol(nptr, &endptr, 10); + if (*endptr != '\0') return false; + if (tmp == LONG_MAX || tmp == LONG_MIN) return false; + n = tmp; + return true; + } + +} // namespace Util diff --git a/src/plugins/exiv2/utils.hpp b/src/plugins/exiv2/utils.hpp @@ -0,0 +1,165 @@ +// ********************************************************* -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file utils.hpp + @brief A collection of utility functions + @version $Rev: 560 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 12-Dec-03, ahu: created + */ +#ifndef UTILS_HPP_ +#define UTILS_HPP_ + +// ********************************************************************* +// included header files +// + standard includes +#include <string> + +// ********************************************************************* +// namespace extensions +/*! + @brief Contains utility classes and functions. Most of these are + wrappers for common C functions that do not require pointers + and memory considerations. +*/ +namespace Util { + +// ********************************************************************* +// class definitions + +/*! + @brief Parse the command line options of a program. + + A wrapper around the POSIX %getopt(3) function. Parses the command line + options and passes each option to virtual option(). A derived class + implements this method to handle options as needed. Similarly, + remaining non-option parameters are passed to the virtual nonoption() + method. + */ +class Getopt { +public: + //! Default constructor. + Getopt() : errcnt_(0) {} + + //! Destructor. + virtual ~Getopt() {} + + /*! + @brief Parse command line arguments. + + Parses the command line arguments. Calls option() with the + character value of the option and its argument (if any) for each + recognized option and with ':' or '?' for unrecognized options. + See the manual pages for %getopt(3) for details. In addition, + nonoption() is invoked for each remaining non-option parameter on + the command line. + + @param argc Argument count as passed to main() on program invocation. + @param argv Argument array as passed to main() on program invocation. + @param optstring String containing the legitimate option characters. + + @return Number of errors (the sum of the return values from option() + and nonoption()). + */ + int getopt(int argc, char* const argv[], const std::string& optstring); + + /*! + @brief Callback used by getopt() to pass on each option and its + argument (if any). + + Implement this method in a derived class to handle the options as + needed. See the manual pages for %getopt(3) for further details, in + particular, the semantics of optarg and optopt. + + @param opt Value of the option character as returned by %getopt(3). + @param optarg The corresponding option argument. + @param optopt The actual option character in case of an unrecognized + option or a missing option argument (opt is '?' or ':'). + + @return 0 if successful, 1 in case of an error. + */ + virtual int option(int opt, const std::string& optarg, int optopt) = 0; + + /*! + @brief Callback used by getopt() to pass on each non-option parameter + found on the command line. + + Implement this method in a derived class to handle the non-option + parameters as needed. The default implementation ignores all non-option + parameters. + + @param argv The non-option parameter from the command line. + + @return 0 if successful, 1 in case of an error. + */ + virtual int nonoption(const std::string& argv) { return 0; } + + //! Program name (argv[0]) + const std::string& progname() const { return progname_; } + + //! Total number of errors returned by calls to option() + int errcnt() const { return errcnt_; } + +private: + std::string progname_; + int errcnt_; +}; + +// ********************************************************************* +// free functions + + /*! + @brief Get the directory component from the \em path string. + See %dirname(3). + + This function can handle Windows paths to some extent: c:\\bar should + be fine, \\\\bigsrv\\foo also, but \\\\bigsrv alone doesn't work. + */ + std::string dirname(const std::string& path); + + /*! + @brief Get the filename component from the \em path string. + See %basename(3). If the \em delsuffix parameter is true, + the suffix will be removed. + + This function can handle Windows paths to some extent: c:\\bar should + be fine, \\\\bigsrv\\foo also, but \\\\bigsrv alone doesn't work. + */ + std::string basename(const std::string& path, bool delsuffix =false); + + /*! + @brief Get the suffix from the path string. Normally, the suffix + is the substring of the basename of path from the last '.' + to the end of the string. + */ + std::string suffix(const std::string& path); + + /*! + @brief Convert a C string to a long value, which is returned in n. + Returns true if the conversion is successful, else false. + n is not modified if the conversion is unsuccessful. See strtol(2). + */ + bool strtol(const char* nptr, long& n); + +} // namespace Util + +#endif // #ifndef UTILS_HPP_ diff --git a/src/plugins/exiv2/value.cpp b/src/plugins/exiv2/value.cpp @@ -0,0 +1,557 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/* + File: value.cpp + Version: $Rev: 560 $ + Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net> + History: 26-Jan-04, ahu: created + 11-Feb-04, ahu: isolated as a component + 31-Jul-04, brad: added Time, Date and String values + */ +// ***************************************************************************** +#include "rcsid.hpp" +EXIV2_RCSID("@(#) $Id: value.cpp 560 2005-04-17 11:51:32Z ahuggel $"); + +// ***************************************************************************** +// included header files +#include "value.hpp" +#include "types.hpp" +#include "error.hpp" + +// + standard includes +#include <iostream> +#include <iomanip> +#include <sstream> +#include <cassert> +#include <ctime> + +// ***************************************************************************** +// class member definitions +namespace Exiv2 { + + Value& Value::operator=(const Value& rhs) + { + if (this == &rhs) return *this; + type_ = rhs.type_; + return *this; + } + + Value::AutoPtr Value::create(TypeId typeId) + { + AutoPtr value; + switch (typeId) { + case invalidTypeId: + value = AutoPtr(new DataValue(invalidTypeId)); + break; + case unsignedByte: + value = AutoPtr(new DataValue(unsignedByte)); + break; + case asciiString: + value = AutoPtr(new AsciiValue); + break; + case unsignedShort: + value = AutoPtr(new ValueType<uint16_t>); + break; + case unsignedLong: + value = AutoPtr(new ValueType<uint32_t>); + break; + case unsignedRational: + value = AutoPtr(new ValueType<URational>); + break; + case invalid6: + value = AutoPtr(new DataValue(invalid6)); + break; + case undefined: + value = AutoPtr(new DataValue); + break; + case signedShort: + value = AutoPtr(new ValueType<int16_t>); + break; + case signedLong: + value = AutoPtr(new ValueType<int32_t>); + break; + case signedRational: + value = AutoPtr(new ValueType<Rational>); + break; + case string: + value = AutoPtr(new StringValue); + break; + case date: + value = AutoPtr(new DateValue); + break; + case time: + value = AutoPtr(new TimeValue); + break; + case comment: + value = AutoPtr(new CommentValue); + break; + default: + value = AutoPtr(new DataValue(typeId)); + break; + } + return value; + } // Value::create + + std::string Value::toString() const + { + std::ostringstream os; + write(os); + return os.str(); + } + + DataValue& DataValue::operator=(const DataValue& rhs) + { + if (this == &rhs) return *this; + Value::operator=(rhs); + value_ = rhs.value_; + return *this; + } + + void DataValue::read(const byte* buf, long len, ByteOrder byteOrder) + { + // byteOrder not needed + value_.assign(buf, buf + len); + } + + void DataValue::read(const std::string& buf) + { + std::istringstream is(buf); + int tmp; + value_.clear(); + while (is >> tmp) { + value_.push_back(static_cast<byte>(tmp)); + } + } + + long DataValue::copy(byte* buf, ByteOrder byteOrder) const + { + // byteOrder not needed + return static_cast<long>( + std::copy(value_.begin(), value_.end(), buf) - buf + ); + } + + long DataValue::size() const + { + return static_cast<long>(value_.size()); + } + + DataValue* DataValue::clone_() const + { + return new DataValue(*this); + } + + std::ostream& DataValue::write(std::ostream& os) const + { + std::vector<byte>::size_type end = value_.size(); + for (std::vector<byte>::size_type i = 0; i != end; ++i) { + os << static_cast<int>(value_[i]) << " "; + } + return os; + } + + StringValueBase& StringValueBase::operator=(const StringValueBase& rhs) + { + if (this == &rhs) return *this; + Value::operator=(rhs); + value_ = rhs.value_; + return *this; + } + + void StringValueBase::read(const std::string& buf) + { + value_ = buf; + } + + void StringValueBase::read(const byte* buf, long len, ByteOrder byteOrder) + { + // byteOrder not needed + value_ = std::string(reinterpret_cast<const char*>(buf), len); + } + + long StringValueBase::copy(byte* buf, ByteOrder byteOrder) const + { + // byteOrder not needed + return static_cast<long>( + value_.copy(reinterpret_cast<char*>(buf), value_.size()) + ); + } + + long StringValueBase::size() const + { + return static_cast<long>(value_.size()); + } + + std::ostream& StringValueBase::write(std::ostream& os) const + { + return os << value_; + } + + StringValue& StringValue::operator=(const StringValue& rhs) + { + if (this == &rhs) return *this; + StringValueBase::operator=(rhs); + return *this; + } + + StringValue* StringValue::clone_() const + { + return new StringValue(*this); + } + + AsciiValue& AsciiValue::operator=(const AsciiValue& rhs) + { + if (this == &rhs) return *this; + StringValueBase::operator=(rhs); + return *this; + } + + void AsciiValue::read(const std::string& buf) + { + value_ = buf; + if (value_[value_.size()-1] != '\0') value_ += '\0'; + } + + AsciiValue* AsciiValue::clone_() const + { + return new AsciiValue(*this); + } + + std::ostream& AsciiValue::write(std::ostream& os) const + { + // Strip all trailing '\0's (if any) + std::string::size_type pos = value_.find_last_not_of('\0'); + return os << value_.substr(0, pos + 1); + } + + CommentValue::CharsetTable::CharsetTable(CharsetId charsetId, + const char* name, + const char* code) + : charsetId_(charsetId), name_(name), code_(code) + { + } + + //! Lookup list of supported IFD type information + const CommentValue::CharsetTable CommentValue::CharsetInfo::charsetTable_[] = { + CharsetTable(ascii, "Ascii", "ASCII\0\0\0"), + CharsetTable(jis, "Jis", "JIS\0\0\0\0\0"), + CharsetTable(unicode, "Unicode", "UNICODE\0"), + CharsetTable(undefined, "Undefined", "\0\0\0\0\0\0\0\0"), + CharsetTable(invalidCharsetId, "InvalidCharsetId", "\0\0\0\0\0\0\0\0"), + CharsetTable(lastCharsetId, "InvalidCharsetId", "\0\0\0\0\0\0\0\0") + }; + + const char* CommentValue::CharsetInfo::name(CharsetId charsetId) + { + return charsetTable_[ charsetId < lastCharsetId ? charsetId : undefined ].name_; + } + + const char* CommentValue::CharsetInfo::code(CharsetId charsetId) + { + return charsetTable_[ charsetId < lastCharsetId ? charsetId : undefined ].code_; + } + + CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByName( + const std::string& name) + { + int i = 0; + for (; charsetTable_[i].charsetId_ != lastCharsetId + && charsetTable_[i].name_ != name; ++i) {} + return charsetTable_[i].charsetId_ == lastCharsetId ? + invalidCharsetId : charsetTable_[i].charsetId_; + } + + CommentValue::CharsetId CommentValue::CharsetInfo::charsetIdByCode( + const std::string& code) + { + int i = 0; + for (; charsetTable_[i].charsetId_ != lastCharsetId + && std::string(charsetTable_[i].code_, 8) != code; ++i) {} + return charsetTable_[i].charsetId_ == lastCharsetId ? + invalidCharsetId : charsetTable_[i].charsetId_; + } + + CommentValue::CommentValue(const std::string& comment) + : StringValueBase(Exiv2::undefined) + { + read(comment); + } + + CommentValue& CommentValue::operator=(const CommentValue& rhs) + { + if (this == &rhs) return *this; + StringValueBase::operator=(rhs); + return *this; + } + + void CommentValue::read(const std::string& comment) + { + std::string c = comment; + CharsetId charsetId = undefined; + if (comment.length() > 8 && comment.substr(0, 8) == "charset=") { + std::string::size_type pos = comment.find_first_of(' '); + std::string name = comment.substr(8, pos-8); + // Strip quotes (so you can also to specify the charset without quotes) + if (name[0] == '"') name = name.substr(1); + if (name[name.length()-1] == '"') name = name.substr(0, name.length()-1); + charsetId = CharsetInfo::charsetIdByName(name); + if (charsetId == invalidCharsetId) throw Error(28, name); + c.clear(); + if (pos != std::string::npos) c = comment.substr(pos+1); + } + const std::string code(CharsetInfo::code(charsetId), 8); + StringValueBase::read(code + c); + } + + std::ostream& CommentValue::write(std::ostream& os) const + { + CharsetId charsetId = this->charsetId(); + if (charsetId != undefined) { + os << "charset=\"" << CharsetInfo::name(charsetId) << "\" "; + } + return os << comment(); + } + + std::string CommentValue::comment() const + { + if (value_.length() >= 8) return value_.substr(8); + return ""; + } + + CommentValue::CharsetId CommentValue::charsetId() const + { + CharsetId charsetId = undefined; + if (value_.length() >= 8) { + const std::string code = value_.substr(0, 8); + charsetId = CharsetInfo::charsetIdByCode(code); + } + return charsetId; + } + + CommentValue* CommentValue::clone_() const + { + return new CommentValue(*this); + } + + DateValue::DateValue(int year, int month, int day) + : Value(date) + { + date_.year = year; + date_.month = month; + date_.day = day; + } + + DateValue& DateValue::operator=(const DateValue& rhs) + { + if (this == &rhs) return *this; + Value::operator=(rhs); + date_.year = rhs.date_.year; + date_.month = rhs.date_.month; + date_.day = rhs.date_.day; + return *this; + } + + void DateValue::read(const byte* buf, long len, ByteOrder byteOrder) + { + // byteOrder not needed + // Hard coded to read Iptc style dates + if (len != 8) throw Error(29); + int scanned = sscanf(reinterpret_cast<const char*>(buf), + "%4d%2d%2d", + &date_.year, &date_.month, &date_.day); + if (scanned != 3) throw Error(29); + } + + void DateValue::read(const std::string& buf) + { + // byteOrder not needed + // Hard coded to read Iptc style dates + if (buf.length() < 8) throw Error(29); + int scanned = sscanf(buf.data(), + "%4d-%d-%d", + &date_.year, &date_.month, &date_.day); + if (scanned != 3) throw Error(29); + } + + void DateValue::setDate( const Date& src ) + { + date_.year = src.year; + date_.month = src.month; + date_.day = src.day; + } + + long DateValue::copy(byte* buf, ByteOrder byteOrder) const + { + // byteOrder not needed + // sprintf wants to add the null terminator, so use oversized buffer + char temp[9]; + + int wrote = sprintf( temp, "%04d%02d%02d", + date_.year, date_.month, date_.day); + assert(wrote == 8); + memcpy(buf, temp, 8); + return 8; + } + + long DateValue::size() const + { + return 8; + } + + DateValue* DateValue::clone_() const + { + return new DateValue(*this); + } + + std::ostream& DateValue::write(std::ostream& os) const + { + return os << date_.year << '-' << std::right + << std::setw(2) << std::setfill('0') << date_.month << '-' + << std::setw(2) << std::setfill('0') << date_.day; + } + + long DateValue::toLong(long n) const + { + // Range of tm struct is limited to about 1970 to 2038 + // This will return -1 if outside that range + std::tm tms; + memset(&tms, 0, sizeof(tms)); + tms.tm_mday = date_.day; + tms.tm_mon = date_.month - 1; + tms.tm_year = date_.year - 1900; + return static_cast<long>(std::mktime(&tms)); + } + + TimeValue::TimeValue(int hour, int minute, + int second, int tzHour, + int tzMinute) + : Value(date) + { + time_.hour=hour; + time_.minute=minute; + time_.second=second; + time_.tzHour=tzHour; + time_.tzMinute=tzMinute; + } + + TimeValue& TimeValue::operator=(const TimeValue& rhs) + { + if (this == &rhs) return *this; + Value::operator=(rhs); + memcpy(&time_, &rhs.time_, sizeof(time_)); + return *this; + } + + void TimeValue::read(const byte* buf, long len, ByteOrder byteOrder) + { + // byteOrder not needed + // Hard coded to read Iptc style times + if (len != 11) throw Error(30); + char plusMinus; + int scanned = sscanf(reinterpret_cast<const char*>(buf), + "%2d%2d%2d%1c%2d%2d", + &time_.hour, &time_.minute, &time_.second, + &plusMinus, &time_.tzHour, &time_.tzMinute ); + + if (scanned != 6) throw Error(30); + if (plusMinus == '-') { + time_.tzHour *= -1; + time_.tzMinute *= -1; + } + } + + void TimeValue::read(const std::string& buf) + { + // byteOrder not needed + // Hard coded to read Iptc style times + if (buf.length() < 9) throw Error(30); + char plusMinus; + int scanned = sscanf(buf.data(), + "%d:%d:%d%1c%d:%d", + &time_.hour, &time_.minute, &time_.second, + &plusMinus, &time_.tzHour, &time_.tzMinute ); + + if (scanned != 6) throw Error(30); + if (plusMinus == '-') { + time_.tzHour *= -1; + time_.tzMinute *= -1; + } + } + + void TimeValue::setTime( const Time& src ) + { + memcpy(&time_, &src, sizeof(time_)); + } + + long TimeValue::copy(byte* buf, ByteOrder byteOrder) const + { + // byteOrder not needed + // sprintf wants to add the null terminator, so use oversized buffer + char temp[12]; + char plusMinus = '+'; + if (time_.tzHour < 0 || time_.tzMinute < 0) plusMinus = '-'; + + int wrote = sprintf(temp, + "%02d%02d%02d%1c%02d%02d", + time_.hour, time_.minute, time_.second, + plusMinus, abs(time_.tzHour), abs(time_.tzMinute)); + + assert(wrote == 11); + memcpy(buf, temp, 11); + return 11; + } + + long TimeValue::size() const + { + return 11; + } + + TimeValue* TimeValue::clone_() const + { + return new TimeValue(*this); + } + + std::ostream& TimeValue::write(std::ostream& os) const + { + char plusMinus = '+'; + if (time_.tzHour < 0 || time_.tzMinute < 0) plusMinus = '-'; + + return os << std::right + << std::setw(2) << std::setfill('0') << time_.hour << ':' + << std::setw(2) << std::setfill('0') << time_.minute << ':' + << std::setw(2) << std::setfill('0') << time_.second << plusMinus + << std::setw(2) << std::setfill('0') << abs(time_.tzHour) << ':' + << std::setw(2) << std::setfill('0') << abs(time_.tzMinute); + } + + long TimeValue::toLong(long n) const + { + // Returns number of seconds in the day in UTC. + long result = (time_.hour - time_.tzHour) * 60 * 60; + result += (time_.minute - time_.tzMinute) * 60; + result += time_.second; + if (result < 0) { + result += 86400; + } + return result; + } + +} // namespace Exiv2 diff --git a/src/plugins/exiv2/value.hpp b/src/plugins/exiv2/value.hpp @@ -0,0 +1,1225 @@ +// ***************************************************************** -*- C++ -*- +/* + * Copyright (C) 2004, 2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +/*! + @file value.hpp + @brief Value interface and concrete subclasses + @version $Rev: 560 $ + @author Andreas Huggel (ahu) + <a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a> + @date 09-Jan-04, ahu: created + 11-Feb-04, ahu: isolated as a component + 31-Jul-04, brad: added Time, Data and String values + */ +#ifndef VALUE_HPP_ +#define VALUE_HPP_ + +// ***************************************************************************** +// included header files +#include "types.hpp" + +// + standard includes +#include <string> +#include <vector> +#include <iostream> +#include <sstream> +#include <memory> + +// ***************************************************************************** +// namespace extensions +namespace Exiv2 { + +// ***************************************************************************** +// class definitions + + /*! + @brief Common interface for all types of values used with metadata. + + The interface provides a uniform way to access values independent from + their actual C++ type for simple tasks like reading the values from a + string or data buffer. For other tasks, like modifying values you may + need to downcast it to the actual subclass of %Value so that you can + access the subclass specific interface. + */ + class Value { + public: + //! Shortcut for a %Value auto pointer. + typedef std::auto_ptr<Value> AutoPtr; + + //! @name Creators + //@{ + //! Constructor, taking a type id to initialize the base class with + explicit Value(TypeId typeId) + : type_(typeId) {} + //! Copy constructor + Value(const Value& rhs) + : type_(rhs.type_) {} + //! Virtual destructor. + virtual ~Value() {} + //@} + + //! @name Manipulators + //@{ + /*! + @brief Read the value from a character buffer. + + @param buf Pointer to the data buffer to read from + @param len Number of bytes in the data buffer + @param byteOrder Applicable byte order (little or big endian). + */ + virtual void read(const byte* buf, long len, ByteOrder byteOrder) =0; + /*! + @brief Set the value from a string buffer. The format of the string + corresponds to that of the write() method, i.e., a string + obtained through the write() method can be read by this + function. + + @param buf The string to read from. + */ + virtual void read(const std::string& buf) =0; + /*! + @brief Set the data area, if the value has one by copying (cloning) + the buffer pointed to by buf. + + Values may have a data area, which can contain additional + information besides the actual value. This method is used to set such + a data area. + + @param buf Pointer to the source data area + @param len Size of the data area + @return Return -1 if the value has no data area, else 0. + */ + virtual int setDataArea(const byte* buf, long len) { return -1; } + //@} + + //! @name Accessors + //@{ + //! 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 + it will be deleted. + */ + AutoPtr clone() const { return AutoPtr(clone_()); } + /*! + @brief Write value to a data buffer. + + 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 bytes written. + */ + virtual long copy(byte* buf, ByteOrder byteOrder) const =0; + //! Return the number of components of the value + virtual long count() const =0; + //! Return the size of the value in bytes + virtual long size() const =0; + /*! + @brief Write the value to an output stream. You do not usually have + to use this function; it is used for the implementation of + the output operator for %Value, + operator<<(std::ostream &os, const Value &value). + */ + 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. + + @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. + + @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. + + @return The converted value. + */ + virtual Rational toRational(long n =0) const =0; + //! Return the size of the data area, 0 if there is none. + virtual long sizeDataArea() const { return 0; } + /*! + @brief Return a copy of the data area if the value has one. The + caller owns this copy and DataBuf ensures that it will be + deleted. + + Values may have a data area, which can contain additional + information besides the actual value. This method is used to access + such a data area. + + @return A DataBuf containing a copy of the data area or an empty + DataBuf if the value does not have a data area assigned. + */ + virtual DataBuf dataArea() const { return DataBuf(0, 0); }; + //@} + + /*! + @brief A (simple) factory to create a Value type. + + The following Value subclasses are created depending on typeId:<BR><BR> + <TABLE> + <TR><TD class="indexkey"><B>typeId</B></TD><TD class="indexvalue"><B>%Value subclass</B></TD></TR> + <TR><TD class="indexkey">invalidTypeId</TD><TD class="indexvalue">%DataValue(invalidTypeId)</TD></TR> + <TR><TD class="indexkey">unsignedByte</TD><TD class="indexvalue">%DataValue(unsignedByte)</TD></TR> + <TR><TD class="indexkey">asciiString</TD><TD class="indexvalue">%AsciiValue</TD></TR> + <TR><TD class="indexkey">string</TD><TD class="indexvalue">%StringValue</TD></TR> + <TR><TD class="indexkey">unsignedShort</TD><TD class="indexvalue">%ValueType &lt; uint16_t &gt;</TD></TR> + <TR><TD class="indexkey">unsignedLong</TD><TD class="indexvalue">%ValueType &lt; uint32_t &gt;</TD></TR> + <TR><TD class="indexkey">unsignedRational</TD><TD class="indexvalue">%ValueType &lt; URational &gt;</TD></TR> + <TR><TD class="indexkey">invalid6</TD><TD class="indexvalue">%DataValue(invalid6)</TD></TR> + <TR><TD class="indexkey">undefined</TD><TD class="indexvalue">%DataValue</TD></TR> + <TR><TD class="indexkey">signedShort</TD><TD class="indexvalue">%ValueType &lt; int16_t &gt;</TD></TR> + <TR><TD class="indexkey">signedLong</TD><TD class="indexvalue">%ValueType &lt; int32_t &gt;</TD></TR> + <TR><TD class="indexkey">signedRational</TD><TD class="indexvalue">%ValueType &lt; Rational &gt;</TD></TR> + <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"><EM>default:</EM></TD><TD class="indexvalue">%DataValue(typeId)</TD></TR> + </TABLE> + + @param typeId Type of the value. + @return Auto-pointer to the newly created Value. The caller owns this + copy and the auto-pointer ensures that it will be deleted. + */ + static AutoPtr create(TypeId typeId); + + protected: + /*! + @brief Assignment operator. Protected so that it can only be used + by subclasses but not directly. + */ + Value& operator=(const Value& rhs); + + private: + //! Internal virtual copy constructor. + virtual Value* clone_() const =0; + // DATA + TypeId type_; //!< Type of the data + + }; // class Value + + //! Output operator for Value types + inline std::ostream& operator<<(std::ostream& os, const Value& value) + { + return value.write(os); + } + + //! %Value for an undefined data type. + class DataValue : public Value { + public: + //! Shortcut for a %DataValue auto pointer. + typedef std::auto_ptr<DataValue> AutoPtr; + + //! @name Creators + //@{ + //! Default constructor. + DataValue(TypeId typeId =undefined) : Value(typeId) {} + //! Constructor + DataValue(const byte* buf, + long len, ByteOrder byteOrder =invalidByteOrder, + TypeId typeId =undefined) + : Value(typeId) { read(buf, len, byteOrder); } + //! Virtual destructor. + virtual ~DataValue() {} + //@} + + //! @name Manipulators + //@{ + //! Assignment operator. + DataValue& operator=(const DataValue& rhs); + /*! + @brief Read the value from a character buffer. + + @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. + */ + virtual void read(const byte* buf, + long len, + ByteOrder byteOrder =invalidByteOrder); + //! Set the data from a string of integer values (e.g., "0 1 2 3") + virtual void read(const std::string& buf); + //@} + + //! @name Accessors + //@{ + AutoPtr clone() const { return AutoPtr(clone_()); } + /*! + @brief Write value to a character data buffer. + + @note The byte order is required by the interface but not used by this + method, so just use the default. + + 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 Byte order. Not needed. + @return Number of characters written. + */ + virtual long copy(byte* buf, ByteOrder byteOrder =invalidByteOrder) const; + virtual long count() const { return size(); } + virtual long size() const; + virtual std::ostream& write(std::ostream& os) 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 + { return Rational(value_[n], 1); } + //@} + + private: + //! Internal virtual copy constructor. + virtual DataValue* clone_() const; + // DATA + std::vector<byte> value_; + + }; // class DataValue + + /*! + @brief Abstract base class for a string based %Value type. + + Uses a std::string to store the value and implements defaults for + most operations. + */ + class StringValueBase : public Value { + public: + //! Shortcut for a %StringValueBase auto pointer. + typedef std::auto_ptr<StringValueBase> AutoPtr; + + //! @name Creators + //@{ + //! Constructor for subclasses + StringValueBase(TypeId typeId) + : Value(typeId) {} + //! Constructor for subclasses + StringValueBase(TypeId typeId, const std::string& buf) + : Value(typeId) { read(buf); } + //! Copy constructor + StringValueBase(const StringValueBase& rhs) + : Value(rhs), value_(rhs.value_) {} + + //! Virtual destructor. + virtual ~StringValueBase() {} + //@} + + //! @name Manipulators + //@{ + //! Assignment operator. + StringValueBase& operator=(const StringValueBase& rhs); + //! Read the value from buf. This default implementation uses buf as it is. + virtual void read(const std::string& buf); + /*! + @brief Read the value from a character buffer. + + @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. + */ + virtual void 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 size(); } + virtual long size() 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 + { return Rational(value_[n], 1); } + virtual std::ostream& write(std::ostream& os) const; + //@} + + protected: + //! Internal virtual copy constructor. + virtual StringValueBase* clone_() const =0; + // DATA + std::string value_; //!< Stores the string value. + + }; // class StringValueBase + + /*! + @brief %Value for string type. + + This can be a plain Ascii string or a multipe byte encoded string. It is + left to caller to decode and encode the string to and from readable + text if that is required. + */ + class StringValue : public StringValueBase { + public: + //! Shortcut for a %StringValue auto pointer. + typedef std::auto_ptr<StringValue> AutoPtr; + + //! @name Creators + //@{ + //! Default constructor. + StringValue() + : StringValueBase(string) {} + //! Constructor + StringValue(const std::string& buf) + : StringValueBase(string, buf) {} + //! Copy constructor + StringValue(const StringValue& rhs) + : StringValueBase(rhs) {} + //! Virtual destructor. + virtual ~StringValue() {} + //@} + + //! @name Manipulators + //@{ + StringValue& operator=(const StringValue& rhs); + //@} + + //! @name Accessors + //@{ + AutoPtr clone() const { return AutoPtr(clone_()); } + //@} + + private: + //! Internal virtual copy constructor. + virtual StringValue* clone_() const; + + }; // class StringValue + + /*! + @brief %Value for an Ascii string type. + + This class is for null terminated single byte Ascii strings. + This class also ensures that the string is null terminated. + */ + class AsciiValue : public StringValueBase { + public: + //! Shortcut for a %AsciiValue auto pointer. + typedef std::auto_ptr<AsciiValue> AutoPtr; + + //! @name Creators + //@{ + //! Default constructor. + AsciiValue() + : StringValueBase(asciiString) {} + //! Constructor + AsciiValue(const std::string &buf) + : StringValueBase(asciiString, buf) {} + //! Copy constructor + AsciiValue(const AsciiValue& rhs) + : StringValueBase(rhs) {} + //! Virtual destructor. + virtual ~AsciiValue() {} + //@} + + //! @name Manipulators + //@{ + //! Assignment operator + AsciiValue& operator=(const AsciiValue& rhs); + /*! + @brief Set the value to that of the string buf. Overrides base class + to append a terminating '\\0' character if buf doesn't end + with '\\0'. + */ + virtual void read(const std::string& buf); + //@} + + //! @name Accessors + //@{ + AutoPtr clone() const { return AutoPtr(clone_()); } + /*! + @brief Write the value to an output stream. Any trailing '\\0' + characters of the ASCII value are stripped and not written to + the output stream. + */ + virtual std::ostream& write(std::ostream& os) const; + //@} + + private: + //! Internal virtual copy constructor. + virtual AsciiValue* clone_() const; + + }; // class AsciiValue + + /*! + @brief %Value for an Exif comment. + + This can be a plain Ascii string or a multipe byte encoded string. The + comment is expected to be encoded in the character set indicated (default + undefined), but this is not checked. It is left to caller to decode and + encode the string to and from readable text if that is required. + */ + class CommentValue : public StringValueBase { + public: + //! Character set identifiers for the character sets defined by %Exif + enum CharsetId { ascii, jis, unicode, undefined, + invalidCharsetId, lastCharsetId }; + //! Information pertaining to the defined character sets + struct CharsetTable { + //! Constructor + CharsetTable(CharsetId charsetId, + const char* name, + const char* code); + CharsetId charsetId_; //!< Charset id + const char* name_; //!< Name of the charset + const char* code_; //!< Code of the charset + }; // struct CharsetTable + //! Charset information lookup functions. Implemented as a static class. + class CharsetInfo { + //! Prevent construction: not implemented. + CharsetInfo() {} + //! Prevent copy-construction: not implemented. + CharsetInfo(const CharsetInfo&); + //! Prevent assignment: not implemented. + CharsetInfo& operator=(const CharsetInfo&); + + public: + //! Return the name for a charset id + static const char* name(CharsetId charsetId); + //! Return the code for a charset id + static const char* code(CharsetId charsetId); + //! Return the charset id for a name + static CharsetId charsetIdByName(const std::string& name); + //! Return the charset id for a code + static CharsetId charsetIdByCode(const std::string& code); + + private: + static const CharsetTable charsetTable_[]; + }; // class CharsetInfo + + //! Shortcut for a %CommentValue auto pointer. + typedef std::auto_ptr<CommentValue> AutoPtr; + + //! @name Creators + //@{ + //! Default constructor. + CommentValue() + : StringValueBase(Exiv2::undefined) {} + //! Constructor, uses read(const std::string& comment) + CommentValue(const std::string& comment); + //! Copy constructor + CommentValue(const CommentValue& rhs) + : StringValueBase(rhs) {} + //! Virtual destructor. + virtual ~CommentValue() {} + //@} + + //! @name Manipulators + //@{ + //! Assignment operator. + CommentValue& operator=(const CommentValue& rhs); + /*! + @brief Read the value from a comment + + The format of \em comment is: + <BR> + <CODE>[charset=["]Ascii|Jis|Unicode|Undefined["] ]comment</CODE> + <BR> + The default charset is Undefined. + + @throw Error if an invalid character set is encountered + */ + void read(const std::string& comment); + //@} + + //! @name Accessors + //@{ + AutoPtr clone() const { return AutoPtr(clone_()); } + /*! + @brief Write the comment in a format which can be read by + read(const std::string& comment). + */ + std::ostream& write(std::ostream& os) const; + //! Return the comment (without a charset="..." prefix) + std::string comment() const; + //! Return the charset id of the comment + CharsetId charsetId() const; + //@} + + private: + //! Internal virtual copy constructor. + virtual CommentValue* clone_() const; + + }; // class CommentValue + + /*! + @brief %Value for simple ISO 8601 dates + + This class is limited to parsing simple date strings in the ISO 8601 + format CCYYMMDD (century, year, month, day). + */ + class DateValue : public Value { + public: + //! Shortcut for a %DateValue auto pointer. + typedef std::auto_ptr<DateValue> AutoPtr; + + //! @name Creators + //@{ + //! Default constructor. + DateValue() : Value(date) { memset(&date_, 0, sizeof(date_)); } + //! Constructor + DateValue(int year, int month, int day); + //! Virtual destructor. + virtual ~DateValue() {} + //@} + + //! Simple Date helper structure + struct Date + { + int year; //!< Year + int month; //!< Month + int day; //!< Day + }; + + //! @name Manipulators + //@{ + //! Assignment operator. + DateValue& operator=(const DateValue& rhs); + /*! + @brief Read the value from a character buffer. + + @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. + + @throw Error in case of an unsupported date format + */ + virtual void read(const byte* buf, + long len, + ByteOrder byteOrder =invalidByteOrder); + /*! + @brief Set the value to that of the string buf. + + @param buf String containing the date + + @throw Error in case of an unsupported date format + */ + virtual void read(const std::string& buf); + //! Set the date + void setDate(const Date& src); + //@} + + //! @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; + //! Return date struct containing date information + 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; + virtual long toLong(long n =0) const; + virtual float toFloat(long n =0) const + { return static_cast<float>(toLong(n)); } + virtual Rational toRational(long n =0) const + { return Rational(toLong(n), 1); } + //@} + + private: + //! Internal virtual copy constructor. + virtual DateValue* clone_() const; + // DATA + Date date_; + + }; // class DateValue + + /*! + @brief %Value for simple ISO 8601 times. + + This class is limited to handling simple time strings in the ISO 8601 + format HHMMSS±HHMM where HHMMSS refers to local hour, minute and + seconds and ±HHMM refers to hours and minutes ahead or behind + Universal Coordinated Time. + */ + class TimeValue : public Value { + public: + //! Shortcut for a %TimeValue auto pointer. + typedef std::auto_ptr<TimeValue> AutoPtr; + + //! @name Creators + //@{ + //! Default constructor. + TimeValue() : Value(time) { memset(&time_, 0, sizeof(time_)); } + //! Constructor + TimeValue(int hour, int minute, int second =0, + int tzHour =0, int tzMinute =0); + + //! Virtual destructor. + virtual ~TimeValue() {} + //@} + + //! Simple Time helper structure + struct Time + { + int hour; //!< Hour + int minute; //!< Minute + int second; //!< Second + int tzHour; //!< Hours ahead or behind UTC + int tzMinute; //!< Minutes ahead or behind UTC + }; + + //! @name Manipulators + //@{ + //! Assignment operator. + TimeValue& operator=(const TimeValue& rhs); + /*! + @brief Read the value from a character buffer. + + @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. + + @throw Error in case of an unsupported time format + */ + virtual void read(const byte* buf, + long len, + ByteOrder byteOrder =invalidByteOrder); + /*! + @brief Set the value to that of the string buf. + + @param buf String containing the time. + + @throw Error in case of an unsupported time format + */ + virtual void read(const std::string& buf); + //! Set the time + void setTime(const Time& src); + //@} + + //! @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; + //! Return time struct containing time information + virtual const Time& getTime() const { return time_; } + 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; + virtual long toLong(long n =0) const; + virtual float toFloat(long n =0) const + { return static_cast<float>(toLong(n)); } + virtual Rational toRational(long n =0) const + { return Rational(toLong(n), 1); } + //@} + + private: + //! Internal virtual copy constructor. + virtual TimeValue* clone_() const; + // DATA + Time time_; + + }; // class TimeValue + //! Template to determine the TypeId for a type T + template<typename T> TypeId getType(); + + //! Specialization for an unsigned short + template<> inline TypeId getType<uint16_t>() { return unsignedShort; } + //! Specialization for an unsigned long + template<> inline TypeId getType<uint32_t>() { return unsignedLong; } + //! Specialization for an unsigned rational + template<> inline TypeId getType<URational>() { return unsignedRational; } + //! Specialization for a signed short + template<> inline TypeId getType<int16_t>() { return signedShort; } + //! Specialization for a signed long + template<> inline TypeId getType<int32_t>() { return signedLong; } + //! Specialization for a signed rational + template<> inline TypeId getType<Rational>() { return signedRational; } + + // No default implementation: let the compiler/linker complain +// template<typename T> inline TypeId getType() { return invalid; } + + /*! + @brief Template for a %Value of a basic type. This is used for unsigned + and signed short, long and rationals. + */ + template<typename T> + class ValueType : public Value { + public: + //! Shortcut for a %ValueType\<T\> auto pointer. + typedef std::auto_ptr<ValueType<T> > AutoPtr; + + //! @name Creators + //@{ + //! Default constructor. + ValueType() : Value(getType<T>()), pDataArea_(0), sizeDataArea_(0) {} + //! Constructor + ValueType(const byte* buf, long len, ByteOrder byteOrder); + //! Constructor + ValueType(const T& val, ByteOrder byteOrder =littleEndian); + //! Copy constructor + ValueType(const ValueType<T>& rhs); + //! Virtual destructor. + virtual ~ValueType(); + //@} + + //! @name Manipulators + //@{ + //! Assignment operator. + ValueType<T>& operator=(const ValueType<T>& rhs); + virtual void read(const byte* buf, long len, ByteOrder byteOrder); + /*! + @brief Set the data from a string of values of type T (e.g., + "0 1 2 3" or "1/2 1/3 1/4" depending on what T is). + Generally, the accepted input format is the same as that + produced by the write() method. + */ + virtual void read(const std::string& buf); + /*! + @brief Set the data area. This method copies (clones) the buffer + pointed to by buf. + */ + virtual int setDataArea(const byte* buf, long len); + //@} + + //! @name Accessors + //@{ + AutoPtr clone() const { return AutoPtr(clone_()); } + virtual long copy(byte* buf, ByteOrder byteOrder) const; + virtual long count() const { return static_cast<long>(value_.size()); } + virtual long size() const; + virtual std::ostream& write(std::ostream& os) const; + virtual long toLong(long n =0) const; + virtual float toFloat(long n =0) const; + virtual Rational toRational(long n =0) const; + //! Return the size of the data area. + virtual long sizeDataArea() const { return sizeDataArea_; } + /*! + @brief Return a copy of the data area in a DataBuf. The caller owns + this copy and DataBuf ensures that it will be deleted. + */ + virtual DataBuf dataArea() const; + //@} + + //! Container for values + typedef std::vector<T> ValueList; + //! Iterator type defined for convenience. + typedef typename std::vector<T>::iterator iterator; + //! Const iterator type defined for convenience. + typedef typename std::vector<T>::const_iterator const_iterator; + + // DATA + /*! + @brief The container for all values. In your application, if you know + what subclass of Value you're dealing with (and possibly the T) + then you can access this STL container through the usual + standard library functions. + */ + ValueList value_; + + private: + //! Internal virtual copy constructor. + virtual ValueType<T>* clone_() const; + + // DATA + //! Pointer to the buffer, 0 if none has been allocated + byte* pDataArea_; + //! The current size of the buffer + long sizeDataArea_; + }; // class ValueType + + //! Unsigned short value type + typedef ValueType<uint16_t> UShortValue; + //! Unsigned long value type + typedef ValueType<uint32_t> ULongValue; + //! Unsigned rational value type + typedef ValueType<URational> URationalValue; + //! Signed short value type + typedef ValueType<int16_t> ShortValue; + //! Signed long value type + typedef ValueType<int32_t> LongValue; + //! Signed rational value type + typedef ValueType<Rational> RationalValue; + +// ***************************************************************************** +// template and inline definitions + + /*! + @brief Read a value of type T from the data buffer. + + We need this template function for the ValueType template classes. + There are only specializations of this function available; no default + implementation is provided. + + @param buf Pointer to the data buffer to read from. + @param byteOrder Applicable byte order (little or big endian). + @return A value of type T. + */ + template<typename T> T getValue(const byte* buf, ByteOrder byteOrder); + // Specialization for a 2 byte unsigned short value. + template<> + inline uint16_t getValue(const byte* buf, ByteOrder byteOrder) + { + return getUShort(buf, byteOrder); + } + // Specialization for a 4 byte unsigned long value. + template<> + inline uint32_t getValue(const byte* buf, ByteOrder byteOrder) + { + return getULong(buf, byteOrder); + } + // Specialization for an 8 byte unsigned rational value. + template<> + inline URational getValue(const byte* buf, ByteOrder byteOrder) + { + return getURational(buf, byteOrder); + } + // Specialization for a 2 byte signed short value. + template<> + inline int16_t getValue(const byte* buf, ByteOrder byteOrder) + { + return getShort(buf, byteOrder); + } + // Specialization for a 4 byte signed long value. + template<> + inline int32_t getValue(const byte* buf, ByteOrder byteOrder) + { + return getLong(buf, byteOrder); + } + // Specialization for an 8 byte signed rational value. + template<> + inline Rational getValue(const byte* buf, ByteOrder byteOrder) + { + return getRational(buf, byteOrder); + } + + /*! + @brief Convert a value of type T to data, write the data to the data buffer. + + We need this template function for the ValueType template classes. + There are only specializations of this function available; no default + implementation is provided. + + @param buf Pointer to the data buffer to write to. + @param t Value to be converted. + @param byteOrder Applicable byte order (little or big endian). + @return The number of bytes written to the buffer. + */ + template<typename T> long toData(byte* buf, T t, ByteOrder byteOrder); + /*! + @brief Specialization to write an unsigned short to the data buffer. + Return the number of bytes written. + */ + template<> + inline long toData(byte* buf, uint16_t t, ByteOrder byteOrder) + { + return us2Data(buf, t, byteOrder); + } + /*! + @brief Specialization to write an unsigned long to the data buffer. + Return the number of bytes written. + */ + template<> + inline long toData(byte* buf, uint32_t t, ByteOrder byteOrder) + { + return ul2Data(buf, t, byteOrder); + } + /*! + @brief Specialization to write an unsigned rational to the data buffer. + Return the number of bytes written. + */ + template<> + inline long toData(byte* buf, URational t, ByteOrder byteOrder) + { + return ur2Data(buf, t, byteOrder); + } + /*! + @brief Specialization to write a signed short to the data buffer. + Return the number of bytes written. + */ + template<> + inline long toData(byte* buf, int16_t t, ByteOrder byteOrder) + { + return s2Data(buf, t, byteOrder); + } + /*! + @brief Specialization to write a signed long to the data buffer. + Return the number of bytes written. + */ + template<> + inline long toData(byte* buf, int32_t t, ByteOrder byteOrder) + { + return l2Data(buf, t, byteOrder); + } + /*! + @brief Specialization to write a signed rational to the data buffer. + Return the number of bytes written. + */ + template<> + inline long toData(byte* buf, Rational t, ByteOrder byteOrder) + { + return r2Data(buf, t, byteOrder); + } + + template<typename T> + ValueType<T>::ValueType(const byte* buf, long len, ByteOrder byteOrder) + : Value(getType<T>()), pDataArea_(0), sizeDataArea_(0) + { + read(buf, len, byteOrder); + } + + template<typename T> + ValueType<T>::ValueType(const T& val, ByteOrder byteOrder) + : Value(getType<T>()), pDataArea_(0), sizeDataArea_(0) + { + read(reinterpret_cast<const byte*>(&val), + TypeInfo::typeSize(typeId()), + byteOrder); + } + + template<typename T> + ValueType<T>::ValueType(const ValueType<T>& rhs) + : Value(rhs), value_(rhs.value_), pDataArea_(0), sizeDataArea_(0) + { + if (rhs.sizeDataArea_ > 0) { + pDataArea_ = new byte[rhs.sizeDataArea_]; + memcpy(pDataArea_, rhs.pDataArea_, rhs.sizeDataArea_); + sizeDataArea_ = rhs.sizeDataArea_; + } + } + + template<typename T> + ValueType<T>::~ValueType() + { + delete[] pDataArea_; + } + + template<typename T> + ValueType<T>& ValueType<T>::operator=(const ValueType<T>& rhs) + { + if (this == &rhs) return *this; + Value::operator=(rhs); + value_ = rhs.value_; + + byte* tmp = 0; + if (rhs.sizeDataArea_ > 0) { + tmp = new byte[rhs.sizeDataArea_]; + memcpy(tmp, rhs.pDataArea_, rhs.sizeDataArea_); + } + delete[] pDataArea_; + pDataArea_ = tmp; + sizeDataArea_ = rhs.sizeDataArea_; + + return *this; + } + + template<typename T> + void ValueType<T>::read(const byte* buf, long len, ByteOrder byteOrder) + { + value_.clear(); + for (long i = 0; i < len; i += TypeInfo::typeSize(typeId())) { + value_.push_back(getValue<T>(buf + i, byteOrder)); + } + } + + template<typename T> + void ValueType<T>::read(const std::string& buf) + { + std::istringstream is(buf); + T tmp; + value_.clear(); + while (is >> tmp) { + value_.push_back(tmp); + } + } + + template<typename T> + long ValueType<T>::copy(byte* buf, ByteOrder byteOrder) const + { + long offset = 0; + typename ValueList::const_iterator end = value_.end(); + for (typename ValueList::const_iterator i = value_.begin(); i != end; ++i) { + offset += toData(buf + offset, *i, byteOrder); + } + return offset; + } + + template<typename T> + long ValueType<T>::size() const + { + return static_cast<long>(TypeInfo::typeSize(typeId()) * value_.size()); + } + + template<typename T> + ValueType<T>* ValueType<T>::clone_() const + { + return new ValueType<T>(*this); + } + + template<typename T> + std::ostream& ValueType<T>::write(std::ostream& os) const + { + typename ValueList::const_iterator end = value_.end(); + typename ValueList::const_iterator i = value_.begin(); + while (i != end) { + os << *i; + if (++i != end) os << " "; + } + return os; + } + // Default implementation + template<typename T> + inline long ValueType<T>::toLong(long n) const + { + return value_[n]; + } + // Specialization for rational + template<> + inline long ValueType<Rational>::toLong(long n) const + { + return value_[n].first / value_[n].second; + } + // Specialization for unsigned rational + template<> + inline long ValueType<URational>::toLong(long n) const + { + return value_[n].first / value_[n].second; + } + // Default implementation + template<typename T> + inline float ValueType<T>::toFloat(long n) const + { + return static_cast<float>(value_[n]); + } + // Specialization for rational + template<> + inline float ValueType<Rational>::toFloat(long n) const + { + return static_cast<float>(value_[n].first) / value_[n].second; + } + // Specialization for unsigned rational + template<> + inline float ValueType<URational>::toFloat(long n) const + { + return static_cast<float>(value_[n].first) / value_[n].second; + } + // Default implementation + template<typename T> + inline Rational ValueType<T>::toRational(long n) const + { + return Rational(value_[n], 1); + } + // Specialization for rational + template<> + inline Rational ValueType<Rational>::toRational(long n) const + { + return Rational(value_[n].first, value_[n].second); + } + // Specialization for unsigned rational + template<> + inline Rational ValueType<URational>::toRational(long n) const + { + return Rational(value_[n].first, value_[n].second); + } + + template<typename T> + inline DataBuf ValueType<T>::dataArea() const + { + return DataBuf(pDataArea_, sizeDataArea_); + } + + template<typename T> + inline int ValueType<T>::setDataArea(const byte* buf, long len) + { + byte* tmp = 0; + if (len > 0) { + tmp = new byte[len]; + memcpy(tmp, buf, len); + } + delete[] pDataArea_; + pDataArea_ = tmp; + sizeDataArea_ = len; + return 0; + } + +} // namespace Exiv2 + +#endif // #ifndef VALUE_HPP_