diff --git a/src/actions.cpp b/src/actions.cpp index cb2f6866..e4ccd5b7 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -20,13 +20,13 @@ */ /* File: actions.cpp - Version: $Name: $ $Revision: 1.9 $ + Version: $Name: $ $Revision: 1.10 $ Author(s): Andreas Huggel (ahu) History: 08-Dec-03, ahu: created */ // ***************************************************************************** #include "rcsid.hpp" -EXIV2_RCSID("@(#) $Name: $ $Revision: 1.9 $ $RCSfile: actions.cpp,v $") +EXIV2_RCSID("@(#) $Name: $ $Revision: 1.10 $ $RCSfile: actions.cpp,v $") // ***************************************************************************** // included header files @@ -103,6 +103,8 @@ namespace Action { 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)); } // TaskFactory c'tor Task::AutoPtr TaskFactory::create(TaskType type) @@ -142,14 +144,16 @@ namespace Action { void Print::printSummary(const Exif::ExifData& exifData) { align_ = 15; + std::cout << std::setw(align_) << std::setfill(' ') << std::left << "Filename" << ": " << path_ << "\n"; printTag(exifData, "Image.OtherTags.Make", "Camera make"); printTag(exifData, "Image.OtherTags.Model", "Camera model"); printTag(exifData, "Image.DateTime.DateTimeOriginal", "Image Timestamp"); - Exif::ExifData::const_iterator md; + // Exposure time: Try ExposureTime, failing that, try ShutterSpeedValue + Exif::ExifData::const_iterator md; std::ostringstream exposure; md = exifData.findKey("Image.CaptureConditions.ExposureTime"); if (md != exifData.end()) { @@ -181,7 +185,7 @@ namespace Action { md = exifData.findKey("Image.CaptureConditions.ApertureValue"); if (md != exifData.end()) { aperture << std::fixed << std::setprecision(1) - << "f/" << exp2f(md->toFloat()/2); + << "F" << exp2f(md->toFloat()/2); } } if (md != exifData.end()) { @@ -189,8 +193,20 @@ namespace Action { << "Aperture" << ": " << aperture.str() << "\n"; } printTag(exifData, "Image.CaptureConditions.Flash", "Flash"); + // Focal length and 35 mm equivalent + // Todo: Calculate 35 mm equivalent a la jhead + md = exifData.findKey("Image.CaptureConditions.FocalLength"); + if (md != exifData.end()) { + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Focal length" << ": " << *md; + md = exifData.findKey("Image.CaptureConditions.FocalLengthIn35mmFilm"); + if (md != exifData.end()) { + std::cout << " (35 mm equivalent: " << *md << ")"; + } + std::cout << "\n"; + } // ISO speed, from ISOSpeedRatings or Canon Makernote - int rc = printTag(exifData, "Image.CaptureConditions.ISOSpeedRatings", "ISO"); + int rc = printTag(exifData, "Image.CaptureConditions.ISOSpeedRatings", "ISO speed"); if (rc == 0) { md = exifData.findKey("Makernote.Canon.CameraSettings1"); if (md != exifData.end() && md->count() >= 16) { @@ -201,6 +217,7 @@ namespace Action { std::cout << "\n"; } } + // Exposure program from ExposureProgram or Canon Makernote rc = printTag(exifData, "Image.CaptureConditions.ExposureProgram", "Exposure"); if (rc == 0) { @@ -213,20 +230,32 @@ namespace Action { std::cout << "\n"; } } - printTag(exifData, "Image.CaptureConditions.FocalLength", "Focal length"); + printTag(exifData, "Image.CaptureConditions.MeteringMode", "Metering mode"); - // Todo: Add size of IFD1 to thumbnail data size + // Exif Resolution + long xdim = 0; + long ydim = 0; + md = exifData.findKey("Image.ImageConfig.PixelXDimension"); + if (md != exifData.end()) xdim = md->toLong(); + md = exifData.findKey("Image.ImageConfig.PixelYDimension"); + if (md != exifData.end()) ydim = md->toLong(); + if (xdim != 0 && ydim != 0) { + std::cout << std::setw(align_) << std::setfill(' ') << std::left + << "Exif Resolution" << ": " + << xdim << " x " << ydim << "\n"; + } + + // Thumbnail std::cout << std::setw(align_) << std::setfill(' ') << std::left << "Thumbnail" << ": "; - switch (exifData.thumbnailType()) { - case Exif::Thumbnail::none: std::cout << "None\n"; break; - case Exif::Thumbnail::jpeg: - std::cout << "JPEG, " << exifData.thumbnailSize() << " Bytes\n"; - break; - case Exif::Thumbnail::tiff: - std::cout << "TIFF, " << exifData.thumbnailSize() << " Bytes\n"; - break; + std::string thumbExt = exifData.thumbnailExtension(); + if (thumbExt.empty()) { + std::cout << "None\n"; + } + else { + std::cout << exifData.thumbnailFormat() << ", " + << exifData.thumbnailSize() << " Bytes\n"; } std::cout << "\n"; @@ -234,7 +263,7 @@ namespace Action { int Print::printTag(const Exif::ExifData& exifData, const std::string& key, - const std::string& label) + const std::string& label) const { int rc = 0; Exif::ExifData::const_iterator md = exifData.findKey(key); @@ -396,6 +425,124 @@ namespace Action { return new Rename(*this); } + int Erase::run(const std::string& path) + try { + path_ = path; + Exif::ExifData exifData; + int rc = exifData.read(path); + if (rc) { + std::cerr << exifReadError(rc, path) << "\n"; + return rc; + } + switch (Params::instance().delTarget_) { + case Params::delExif: rc = eraseExifData(exifData); break; + case Params::delThumb: rc = eraseThumbnail(exifData); break; + } + return rc; + } + catch(const Exif::Error& e) + { + std::cerr << "Exif exception in erase action for file " << path + << ":\n" << e << "\n"; + return 1; + } // Erase::run + + int Erase::eraseThumbnail(Exif::ExifData& exifData) const + { + long delta = exifData.eraseThumbnail(); + if (Params::instance().verbose_) { + std::cout << "Erasing " << delta << " Bytes of thumbnail data\n"; + } + int rc = exifData.write(path_); + if (rc) { + std::cerr << exifWriteError(rc, path_) << "\n"; + } + return rc; + } + + int Erase::eraseExifData(Exif::ExifData& exifData) const + { + // Todo: implement me! + std::cout << "Sorry, the erase action for Exif data has not been implemented yet.\n"; + return 0; + } + + Erase::AutoPtr Erase::clone() const + { + return AutoPtr(dynamic_cast(clone_())); + } + + Task* Erase::clone_() const + { + return new Erase(*this); + } + + int Extract::run(const std::string& path) + try { + path_ = path; + Exif::ExifData exifData; + int rc = exifData.read(path); + if (rc) { + std::cerr << exifReadError(rc, path) << "\n"; + return rc; + } + switch (Params::instance().extractTarget_) { + case Params::extExif: rc = writeExifData(exifData); break; + case Params::extThumb: rc = writeThumbnail(exifData); break; + } + return rc; + } + catch(const Exif::Error& e) + { + std::cerr << "Exif exception in extract action for file " << path + << ":\n" << e << "\n"; + return 1; + } // Extract::run + + int Extract::writeExifData(const Exif::ExifData& exifData) const + { + // Todo: implement me! + std::cout << "Sorry, the extract action for Exif data has not been implemented yet.\n"; + return 0; + } + + int Extract::writeThumbnail(const Exif::ExifData& exifData) const + { + std::string thumb = Util::dirname(path_) + "/" + + Util::basename(path_, true) + "-thumb"; + if (Params::instance().verbose_) { + std::string thumbExt = exifData.thumbnailExtension(); + if (thumbExt.empty()) { + std::cout << "Image does not contain an Exif thumbnail\n"; + } + else { + std::cout << "Writing " + << exifData.thumbnailFormat() << " thumbnail (" + << exifData.thumbnailSize() << " Bytes) to file " + << thumb << thumbExt << "\n"; + } + } + if (!Params::instance().force_ && Util::fileExists(thumb)) { + std::cout << Params::instance().progname() + << ": Overwrite `" << thumb << "'? "; + std::string s; + std::cin >> s; + if (s[0] != 'y' && s[0] != 'Y') return 0; + } + int rc = exifData.writeThumbnail(thumb); + return rc; + } + + Extract::AutoPtr Extract::clone() const + { + return AutoPtr(dynamic_cast(clone_())); + } + + Task* Extract::clone_() const + { + return new Extract(*this); + } + int Adjust::run(const std::string& path) try { adjustment_ = Params::instance().adjustment_; diff --git a/src/actions.hpp b/src/actions.hpp index f0f7bb8e..6b6fc7dd 100644 --- a/src/actions.hpp +++ b/src/actions.hpp @@ -22,7 +22,7 @@ @file actions.hpp @brief Implements base class Task, TaskFactory and the various supported actions (derived from Task). - @version $Name: $ $Revision: 1.4 $ + @version $Name: $ $Revision: 1.5 $ @author Andreas Huggel (ahu) ahuggel@gmx.net @date 11-Dec-03, ahu: created @@ -52,13 +52,13 @@ namespace Exif { namespace Action { //! Enumerates all tasks - enum TaskType { none, adjust, print, rename }; + enum TaskType { none, adjust, print, rename, erase, extract }; // ***************************************************************************** // class definitions /*! - @brief Base class for all concrete actions. + @brief Abstract base class for all concrete actions. Task provides a simple interface that actions must implement and a few commonly used helpers. @@ -81,7 +81,7 @@ namespace Action { //! Internal virtual copy constructor. virtual Task* clone_() const =0; - }; + }; // class Task /*! @brief Task factory. @@ -163,14 +163,14 @@ namespace Action { */ int printTag(const Exif::ExifData& exifData, const std::string& key, - const std::string& label); + const std::string& label) const; private: virtual Task* 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, @@ -178,17 +178,19 @@ namespace Action { */ class Rename : public Task { public: + virtual ~Rename() {} virtual int run(const std::string& path); typedef std::auto_ptr AutoPtr; AutoPtr clone() const; private: virtual Task* 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 AutoPtr; AutoPtr clone() const; @@ -200,8 +202,61 @@ namespace Action { 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 AutoPtr; + AutoPtr clone() const; + + /*! + @brief Delete the thumbnail image, incl IFD1 metadata from the file. + */ + int eraseThumbnail(Exif::ExifData& exifData) const; + /*! + @brief Erase the complete %Exif data block from the file. + */ + int eraseExifData(Exif::ExifData& exifData) const; + + private: + virtual Task* 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 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 Exif::ExifData& exifData) const; + /*! + @brief Write the %Exif data to a file. The filename is composed by + replacing the suffix of the image filename with ".exf". + */ + int writeExifData(const Exif::ExifData& exifData) const; + + private: + virtual Task* clone_() const; + std::string path_; + + }; // class Extract } // namespace Action #endif // #ifndef ACTIONS_HPP_ diff --git a/src/exiv2.cpp b/src/exiv2.cpp index 88f3158f..2fb47890 100644 --- a/src/exiv2.cpp +++ b/src/exiv2.cpp @@ -22,13 +22,13 @@ Abstract: Command line program to display and manipulate image %Exif data File: exiv2.cpp - Version: $Name: $ $Revision: 1.3 $ + Version: $Name: $ $Revision: 1.4 $ Author(s): Andreas Huggel (ahu) History: 10-Dec-03, ahu: created */ // ***************************************************************************** #include "rcsid.hpp" -EXIV2_RCSID("@(#) $Name: $ $Revision: 1.3 $ $RCSfile: exiv2.cpp,v $") +EXIV2_RCSID("@(#) $Name: $ $Revision: 1.4 $ $RCSfile: exiv2.cpp,v $") // ***************************************************************************** // included header files @@ -40,7 +40,7 @@ EXIV2_RCSID("@(#) $Name: $ $Revision: 1.3 $ $RCSfile: exiv2.cpp,v $") #include #include #include -#include +#include // ********************************************************************* // local declarations @@ -115,7 +115,7 @@ void Params::version(std::ostream& os) const void Params::usage(std::ostream& os) const { os << "Usage: " << progname() - << " [ -hVvf ][ -a time ][ -r format ] action file ...\n\n" + << " [ options ] action file ...\n\n" << "Manipulate the Exif metadata of images.\n"; } @@ -125,20 +125,28 @@ void Params::help(std::ostream& os) const os << "\nOptions:\n" << " -h Display this help and exit.\n" << " -V Show the program version and exit.\n" - << " -v Be more verbose during the program run.\n" + << " -v Be extra verbose during the program run.\n" << " -f Do not prompt before overwriting 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" - << " -m mode Print mode for the `print' action. Possible modes are `s'\n" + << " -p mode Print mode for the `print' action. Possible modes are `s'\n" << " for a summary (the default), `i' for interpreted data, `v'\n" - << " for uninterpreted data values and `h' for a hexdump\n" + << " for uninterpreted data values and `h' for a hexdump.\n" + << " -d tgt Delete target for the `delete' action. Possible targets are\n" + << " `e' to delete the whole Exif section (the default) and `t'\n" + << " to delete only the Exif thumbnail from the files.\n" + << " -e tgt Extract target for the `extract' action. Possible targets\n" + << " are `e' to extract the Exif data to a binary file (the\n" + << " default) and `t' to extract only the Exif thumbnail.\n" << " -r fmt Filename format for the `rename' action. The format string\n" << " follows strftime(3). Default filename format is " - << format_ << ".\n" + << format_ << ".\n" << "Actions:\n" << " adjust Adjust the metadata timestamp by the given time. This action\n" << " requires the option -a time.\n" << " print Print the Exif (or other) image metadata.\n" + << " delete Delete the Exif section or Exif thumbnail from the files.\n" + << " extract Extract the Exif data or Exif thumbnail to files.\n" << " rename Rename files according to the metadata create timestamp. The\n" << " filename format can be set with the option -r format.\n\n"; } // Params::help @@ -160,7 +168,7 @@ int Params::option(int opt, const std::string& optarg, int optopt) rc = 1; } break; - case 'm': + case 'p': switch (optarg[0]) { case 's': printMode_ = summary; break; case 'i': printMode_ = interpreted; break; @@ -172,6 +180,26 @@ int Params::option(int opt, const std::string& optarg, int optopt) rc = 1; } break; + case 'd': + switch (optarg[0]) { + case 'e': delTarget_ = delExif; break; + case 't': delTarget_ = delThumb; break; + default: + std::cerr << progname() << ": Unrecognized delete target `" + << optarg << "'\n"; + rc = 1; + } + break; + case 'e': + switch (optarg[0]) { + case 'e': extractTarget_ = extExif; break; + case 't': extractTarget_ = extThumb; break; + default: + std::cerr << progname() << ": Unrecognized extract target `" + << optarg << "'\n"; + rc = 1; + } + break; case ':': std::cerr << progname() << ": Option -" << static_cast(optopt) << " requires an argument\n"; @@ -199,6 +227,8 @@ int Params::nonoption(const std::string& argv) first_ = false; if (argv == "adjust") action_ = Action::adjust; if (argv == "print") action_ = Action::print; + if (argv == "delete") action_ = Action::erase; + if (argv == "extract") action_ = Action::extract; if (argv == "rename") action_ = Action::rename; if (action_ == Action::none) { std::cerr << progname() << ": Unrecognized action `" diff --git a/src/exiv2.hpp b/src/exiv2.hpp index 0c48fcad..2a199c08 100644 --- a/src/exiv2.hpp +++ b/src/exiv2.hpp @@ -21,7 +21,7 @@ /*! @file exiv2.hpp @brief Defines class Params, used for the command line handling of exiv2 - @version $Name: $ $Revision: 1.2 $ + @version $Name: $ $Revision: 1.3 $ @author Andreas Huggel (ahu) ahuggel@gmx.net @date 08-Dec-03, ahu: created @@ -87,6 +87,10 @@ public: //! Enumerates print modes enum PrintMode { summary, interpreted, values, hexdump }; + //! Enumerates delete targets + enum DelTarget { delExif, delThumb }; + //! Enumerates extract targets + enum ExtractTarget { extExif, extThumb }; bool help_; //!< Help option flag. bool version_; //!< Version option flag. @@ -94,6 +98,8 @@ public: bool force_; //!< Force overwrites flag. bool adjust_; //!< Adjustment flag. PrintMode printMode_; //!< Print mode. + DelTarget delTarget_; //!< What to delete. + ExtractTarget extractTarget_; //!< What to extract. //! %Action (integer rather than TaskType to avoid dependency). int action_; @@ -110,13 +116,15 @@ private: @brief Default constructor. Note that optstring_ is initialized here. Private to force instantiation through instance(). */ - Params() : optstring_(":hVvfa:r:m:"), + Params() : optstring_(":hVvfa:r:p:d:e:"), help_(false), version_(false), verbose_(false), force_(false), adjust_(false), printMode_(summary), + delTarget_(delExif), + extractTarget_(extExif), action_(0), adjustment_(0), format_("%Y%m%d_%H%M%S"),