From b5a00fcd5ca3606128c60f038e1f5487b55021c8 Mon Sep 17 00:00:00 2001 From: Andreas Huggel Date: Sun, 7 Dec 2008 10:51:51 +0000 Subject: [PATCH] * Added options -pp and -ep to list and extract preview images * #584: Implemented missing member function * API change, class PreviewImage: Added members to access all preview properties * Minor fix: Suppress XMP encoding error when XMP is not enabled. --- src/actions.cpp | 97 +++++++++++++++++++++++++++++++++++++++++++-- src/actions.hpp | 14 +++++++ src/exiv2.1 | 16 +++++++- src/exiv2.cpp | 69 +++++++++++++++++++++++++++++++- src/exiv2.hpp | 11 +++-- src/jp2image.cpp | 2 +- src/jpgimage.cpp | 2 +- src/pngimage.cpp | 2 +- src/preview.cpp | 24 ++++++++++- src/preview.hpp | 18 +++++++-- src/tiffvisitor.cpp | 2 +- src/xmpsidecar.cpp | 2 +- 12 files changed, 239 insertions(+), 20 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 973a8ed9..6f2a105d 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -51,6 +51,7 @@ EXIV2_RCSID("@(#) $Id$") #include "canonmn.hpp" #include "iptc.hpp" #include "xmp.hpp" +#include "preview.hpp" #include "futils.hpp" #include "i18n.h" // NLS support. @@ -228,6 +229,7 @@ namespace Action { case Params::pmIptc: rc = printIptc(); break; case Params::pmXmp: rc = printXmp(); break; case Params::pmComment: rc = printComment(); break; + case Params::pmPreview: rc = printPreviewList(); break; } return rc; } @@ -770,7 +772,7 @@ namespace Action { } Exiv2::IptcData::const_iterator end = iptcData.end(); Exiv2::IptcData::const_iterator md; - bool manyFiles = Params::instance().files_.size() > 1; + bool const manyFiles = Params::instance().files_.size() > 1; for (md = iptcData.begin(); md != end; ++md) { std::cout << std::setfill(' ') << std::left; if (manyFiles) { @@ -808,7 +810,7 @@ namespace Action { } Exiv2::XmpData::const_iterator end = xmpData.end(); Exiv2::XmpData::const_iterator md; - bool manyFiles = Params::instance().files_.size() > 1; + bool const manyFiles = Params::instance().files_.size() > 1; for (md = xmpData.begin(); md != end; ++md) { std::cout << std::setfill(' ') << std::left; if (manyFiles) { @@ -845,6 +847,36 @@ namespace Action { return 0; } // Print::printComment + int Print::printPreviewList() + { + 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(); + bool const manyFiles = Params::instance().files_.size() > 1; + int cnt = 0; + Exiv2::PreviewManager pm(*image); + Exiv2::PreviewPropertiesList list = pm.getPreviewProperties(); + for (Exiv2::PreviewPropertiesList::const_iterator pos = list.begin(); pos != list.end(); ++pos) { + if (manyFiles) { + std::cout << std::setfill(' ') << std::left << std::setw(20) + << path_ << " "; + } + std::cout << _("Preview") << " " << ++cnt << ": " + << pos->mimeType_ << ", "; + if (pos->width_ != 0 && pos->height_ != 0) { + std::cout << pos->width_ << "x" << pos->height_ << " " + << _("pixels") << ", "; + } + std::cout << pos->size_ << " " << _("bytes") << "\n"; + } + return 0; + } // Print::printPreviewList + Print::AutoPtr Print::clone() const { return AutoPtr(clone_()); @@ -1061,8 +1093,12 @@ namespace Action { if (dontOverwrite(xmpPath)) return 0; rc = metacopy(path_, xmpPath, Exiv2::ImageType::xmp, false); } + if (Params::instance().target_ & Params::ctPreview) { + rc = writePreviews(); + } if ( !(Params::instance().target_ & Params::ctXmpSidecar) - && !(Params::instance().target_ & Params::ctThumb)) { + && !(Params::instance().target_ & Params::ctThumb) + && !(Params::instance().target_ & Params::ctPreview)) { std::string exvPath = newFilePath(path_, ".exv"); if (dontOverwrite(exvPath)) return 0; rc = metacopy(path_, exvPath, Exiv2::ImageType::exv, false); @@ -1118,6 +1154,61 @@ namespace Action { return rc; } // Extract::writeThumbnail + int Extract::writePreviews() 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::PreviewManager pvMgr(*image); + Exiv2::PreviewPropertiesList pvList = pvMgr.getPreviewProperties(); + + const Params::PreviewNumbers& numbers = Params::instance().previewNumbers_; + for (Params::PreviewNumbers::const_iterator n = numbers.begin(); n != numbers.end(); ++n) { + if (*n == 0) { + // Write all previews + for (int num = 0; num < static_cast(pvList.size()); ++num) { + writePreviewFile(pvMgr.getPreviewImage(pvList[num]), num + 1); + } + break; + } + if (*n > static_cast(pvList.size())) { + std::cerr << path_ << ": " << _("Image does not have preview") + << " " << *n << "\n"; + continue; + } + writePreviewFile(pvMgr.getPreviewImage(pvList[*n - 1]), *n); + } + return 0; + } // Extract::writePreviews + + void Extract::writePreviewFile(const Exiv2::PreviewImage& pvImg, int num) const + { + std::string pvFile = newFilePath(path_, "-preview") + Exiv2::toString(num); + std::string pvPath = pvFile + pvImg.extension(); + if (dontOverwrite(pvPath)) return; + if (Params::instance().verbose_) { + std::cout << _("Writing preview") << " " << num << " (" + << pvImg.mimeType() << ", "; + if (pvImg.width() != 0 && pvImg.height() != 0) { + std::cout << pvImg.width() << "x" << pvImg.height() << " " + << _("pixels") << ", "; + } + std::cout << pvImg.size() << " " << _("bytes") << ") " + << _("to file") << " " << pvPath << std::endl; + } + long rc = pvImg.writeFile(pvFile); + if (rc == 0) { + std::cerr << path_ << ": " << _("Image does not have preview") + << " " << num << "\n"; + } + } // Extract::writePreviewFile + Extract::AutoPtr Extract::clone() const { return AutoPtr(clone_()); diff --git a/src/actions.hpp b/src/actions.hpp index c64dad3d..8873d855 100644 --- a/src/actions.hpp +++ b/src/actions.hpp @@ -48,6 +48,7 @@ namespace Exiv2 { class ExifData; class Image; + class PreviewImage; } // ***************************************************************************** @@ -162,6 +163,8 @@ namespace Action { //! Print the Jpeg comment int printComment(); + //! Print list of available preview images + int printPreviewList(); //! Print uninterpreted Iptc information int printIptc(); //! print uninterpreted XMP information @@ -278,6 +281,17 @@ namespace Action { on the format of the Exif thumbnail image. */ int writeThumbnail() const; + /*! + @brief Write preview images to files. + */ + int writePreviews() const; + /*! + @brief Write one preview image to a file. The filename is composed by + removing the suffix from the image filename and appending + "-preview" and the appropriate suffix (".jpg" or ".tif"), + depending on the format of the Exif thumbnail image. + */ + void writePreviewFile(const Exiv2::PreviewImage& pvImg, int num) const; private: virtual Extract* clone_() const; diff --git a/src/exiv2.1 b/src/exiv2.1 index 37817017..e555ae3b 100644 --- a/src/exiv2.1 +++ b/src/exiv2.1 @@ -3,7 +3,7 @@ .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) -.TH EXIV2 1 "Sept 10th, 2008" +.TH EXIV2 1 "Dec 7th, 2008" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -152,6 +152,8 @@ i : IPTC data values x : XMP properties .br c : JPEG comment +.br +p : list available image previews, sorted by preview image size in pixels .TP .B \-P \fIcols\fP Print columns for the Exif taglist ('print' action), allows detailed @@ -209,7 +211,13 @@ be named \fIfile\fP\-thumb.jpg. .TP .B \-e \fItgt\fP Extract target(s) for the 'extract' action. Possible targets are the same -as those for the \fB\-d\fP option, plus a modifier: +as those for the \fB\-d\fP option, plus a target to extract preview +images and a modifier to generate an XMP sidecar file: +.br +p[[, ...]] : Extract preview images. The optional comma separated +list of preview image numbers is used to determine which preview images +to extract. The available preview images and their numbers are displayed +with the 'print' option -pp. .br X : Extract metadata to an XMP sidecar file .xmp. The remaining extract targets determine what metadata to extract to the sidecar @@ -363,6 +371,10 @@ exiv2 \-it img1.jpg img2.jpg Inserts (copies) metadata from img1.exv to img1.jpg and from img2.exv to img2.jpg. .TP +exiv2 \-ep1,2 image.jpg +Extracts previews 1 and 2 from the image to the files image\-preview1.jpg +and image\-preview2.jpg. +.TP exiv2 \-eiX image.jpg Extracts IPTC datasets into an XMP sidecar file image.xmp and in the process converts them to "IPTC Core" XMP schema. diff --git a/src/exiv2.cpp b/src/exiv2.cpp index 14faa2b8..ba67e692 100644 --- a/src/exiv2.cpp +++ b/src/exiv2.cpp @@ -19,7 +19,7 @@ * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA. */ /* - Abstract: Command line program to display and manipulate image %Exif data + Abstract: Command line program to display and manipulate image metadata. File: exiv2.cpp Version: $Rev$ @@ -50,6 +50,7 @@ EXIV2_RCSID("@(#) $Id$") #include #include #include +#include // ***************************************************************************** // local declarations @@ -80,6 +81,17 @@ namespace { int parseCommonTargets(const std::string& optarg, const std::string& action); + /*! + @brief Parse numbers separated by commas into container + @param previewNumbers Container for the numbers + @param optarg Option arguments + @param j Starting index into optarg + @return Number of characters processed + */ + int parsePreviewNumbers(Params::PreviewNumbers& previewNumbers, + const std::string& optarg, + int j); + /*! @brief Parse metadata modification commands from multiple files @param modifyCmds Reference to a structure to store the parsed commands @@ -257,6 +269,7 @@ void Params::help(std::ostream& os) const << _(" i : IPTC data values\n") << _(" x : XMP properties\n") << _(" c : JPEG comment\n") + << _(" p : list available previews\n") << _(" -P cols Print columns for the Exif taglist ('print' action). Valid are:\n") << _(" x : print a column with the tag value\n") << _(" g : group name\n") @@ -282,7 +295,9 @@ void Params::help(std::ostream& os) const " Only JPEG thumbnails can be inserted, they need to be named\n" " -thumb.jpg\n") << _(" -e tgt Extract target(s) for the 'extract' action. Possible targets\n" - " are the same as those for the -d option, plus a modifier:\n" + " are the same as those for the -d option, plus a target to extract\n" + " preview images and a modifier to generate an XMP sidecar file:\n" + " p[[, ...]] : Extract preview images.\n" " X : Extract metadata to an XMP sidecar file .xmp\n") << _(" -r fmt Filename format for the 'rename' action. The format string\n" " follows strftime(3). The following keywords are supported:\n") @@ -457,6 +472,7 @@ int Params::evalPrint(const std::string& optarg) case 'i': printMode_ = pmIptc; break; case 'x': printMode_ = pmXmp; break; case 'c': printMode_ = pmComment; break; + case 'p': printMode_ = pmPreview; break; default: std::cerr << progname() << ": " << _("Unrecognized print mode") << " `" << optarg << "'\n"; @@ -850,6 +866,15 @@ namespace { target |= Params::ctXmpSidecar; if (optarg == "X") target |= Params::ctExif | Params::ctIptc | Params::ctXmp; break; + case 'p': + { + if (strcmp(action.c_str(), "extract") == 0) { + i += parsePreviewNumbers(Params::instance().previewNumbers_, optarg, i + 1); + target |= Params::ctPreview; + break; + } + // fallthrough + } default: std::cerr << Params::instance().progname() << ": " << _("Unrecognized ") << action << " " << _("target") << " `" << optarg[i] << "'\n"; @@ -860,6 +885,46 @@ namespace { return rc ? rc : target; } // parseCommonTargets + int parsePreviewNumbers(Params::PreviewNumbers& previewNumbers, + const std::string& optarg, + int j) + { + size_t k = j; + for (size_t i = j; i < optarg.size(); ++i) { + std::ostringstream os; + for (k = i; k < optarg.size() && isdigit(optarg[k]); ++k) { + os << optarg[k]; + } + if (k > i) { + bool ok = false; + int num = Exiv2::stringTo(os.str(), ok); + if (ok && num >= 0) { + previewNumbers.insert(num); + } + else { + std::cerr << Params::instance().progname() << ": " + << _("Invalid preview number") << ": " << num << "\n"; + } + i = k; + } + if (!(k < optarg.size() && optarg[i] == ',')) break; + } + int ret = static_cast(k - j); + if (ret == 0) { + previewNumbers.insert(0); + } +#ifdef DEBUG + std::cout << "\nThe set now contains: "; + for (Params::PreviewNumbers::const_iterator i = previewNumbers.begin(); + i != previewNumbers.end(); + ++i) { + std::cout << *i << ", "; + } + std::cout << std::endl; +#endif + return k - j; + } // parsePreviewNumbers + bool parseCmdFiles(ModifyCmds& modifyCmds, const Params::CmdFiles& cmdFiles) { diff --git a/src/exiv2.hpp b/src/exiv2.hpp index 78026d2b..751ec240 100644 --- a/src/exiv2.hpp +++ b/src/exiv2.hpp @@ -37,6 +37,7 @@ // + standard includes #include #include +#include #include // ***************************************************************************** @@ -72,7 +73,7 @@ struct CmdIdAndString { @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, + functionality 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. @@ -113,6 +114,8 @@ public: typedef std::vector CmdLines; //! Container to store filenames. typedef std::vector Files; + //! Container for preview image numbers + typedef std::set PreviewNumbers; /*! @brief Controls all access to the global Params instance. @@ -123,7 +126,7 @@ public: void cleanup(); //! Enumerates print modes - enum PrintMode { pmSummary, pmList, pmIptc, pmXmp, pmComment }; + enum PrintMode { pmSummary, pmList, pmIptc, pmXmp, pmComment, pmPreview }; //! Individual items to print enum PrintItem { @@ -147,7 +150,8 @@ public: ctComment = 4, ctThumb = 8, ctXmp = 16, - ctXmpSidecar = 32 + ctXmpSidecar = 32, + ctPreview = 64 }; //! Enumerates the policies to handle existing files in rename action @@ -191,6 +195,7 @@ public: std::string directory_; //!< Location for files to extract/insert std::string suffix_; //!< File extension of the file to insert Files files_; //!< List of non-option arguments. + PreviewNumbers previewNumbers_; //!< List of preview numbers private: //! Pointer to the global Params object. diff --git a/src/jp2image.cpp b/src/jp2image.cpp index 341e7ebe..7756bf69 100644 --- a/src/jp2image.cpp +++ b/src/jp2image.cpp @@ -490,7 +490,7 @@ namespace Exiv2 if (writeXmpFromPacket() == false) { - if (XmpParser::encode(xmpPacket_, xmpData_)) + if (XmpParser::encode(xmpPacket_, xmpData_) > 1) { #ifndef SUPPRESS_WARNINGS std::cerr << "Error: Failed to encode XMP metadata.\n"; diff --git a/src/jpgimage.cpp b/src/jpgimage.cpp index 3186ed77..570be2c2 100644 --- a/src/jpgimage.cpp +++ b/src/jpgimage.cpp @@ -633,7 +633,7 @@ namespace Exiv2 { } } if (writeXmpFromPacket() == false) { - if (XmpParser::encode(xmpPacket_, xmpData_)) { + if (XmpParser::encode(xmpPacket_, xmpData_) > 1) { #ifndef SUPPRESS_WARNINGS std::cerr << "Error: Failed to encode XMP metadata.\n"; #endif diff --git a/src/pngimage.cpp b/src/pngimage.cpp index 0407c582..c0b5d30a 100644 --- a/src/pngimage.cpp +++ b/src/pngimage.cpp @@ -309,7 +309,7 @@ namespace Exiv2 { if (writeXmpFromPacket() == false) { - if (XmpParser::encode(xmpPacket_, xmpData_)) + if (XmpParser::encode(xmpPacket_, xmpData_) > 1) { #ifndef SUPPRESS_WARNINGS std::cerr << "Error: Failed to encode XMP metadata.\n"; diff --git a/src/preview.cpp b/src/preview.cpp index 3c6e7e52..17ac2845 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -695,6 +695,16 @@ namespace Exiv2 { return DataBuf(pData_, size_); } + const byte* PreviewImage::pData() const + { + return pData_; + } + + uint32_t PreviewImage::size() const + { + return size_; + } + std::string PreviewImage::mimeType() const { return properties_.mimeType_; @@ -705,9 +715,19 @@ namespace Exiv2 { return properties_.extension_; } - uint32_t PreviewImage::size() const + uint32_t PreviewImage::width() const { - return size_; + return properties_.width_; + } + + uint32_t PreviewImage::height() const + { + return properties_.height_; + } + + PreviewId PreviewImage::id() const + { + return properties_.id_; } PreviewManager::PreviewManager(const Image& image) diff --git a/src/preview.hpp b/src/preview.hpp index 19dbd08c..ff57d387 100644 --- a/src/preview.hpp +++ b/src/preview.hpp @@ -100,6 +100,10 @@ namespace Exiv2 { @brief Return a pointer to the image data for read-only access. */ const byte* pData() const; + /*! + @brief Return the size of the preview image in bytes. + */ + uint32_t size() const; /*! @brief Write the thumbnail image to a file. @@ -122,9 +126,17 @@ namespace Exiv2 { */ std::string extension() const; /*! - @brief Return the size of the preview image in bytes. - */ - uint32_t size() const; + @brief Return the width of the preview image in pixels. + */ + uint32_t width() const; + /*! + @brief Return the height of the preview image in pixels. + */ + uint32_t height() const; + /*! + @brief Return the preview image type identifier. + */ + PreviewId id() const; //@} private: diff --git a/src/tiffvisitor.cpp b/src/tiffvisitor.cpp index 17faab47..24511aa8 100644 --- a/src/tiffvisitor.cpp +++ b/src/tiffvisitor.cpp @@ -477,7 +477,7 @@ namespace Exiv2 { exifData_.erase(pos); } std::string xmpPacket; - if (XmpParser::encode(xmpPacket, xmpData_)) { + if (XmpParser::encode(xmpPacket, xmpData_) > 1) { #ifndef SUPPRESS_WARNINGS std::cerr << "Error: Failed to encode XMP metadata.\n"; #endif diff --git a/src/xmpsidecar.cpp b/src/xmpsidecar.cpp index f8a4ebd0..aaabc6bf 100644 --- a/src/xmpsidecar.cpp +++ b/src/xmpsidecar.cpp @@ -119,7 +119,7 @@ namespace Exiv2 { copyExifToXmp(exifData_, xmpData_); copyIptcToXmp(iptcData_, xmpData_); if (XmpParser::encode(xmpPacket_, xmpData_, - XmpParser::omitPacketWrapper|XmpParser::useCompactFormat)) { + XmpParser::omitPacketWrapper|XmpParser::useCompactFormat) > 1) { #ifndef SUPPRESS_WARNINGS std::cerr << "Error: Failed to encode XMP metadata.\n"; #endif