diff --git a/include/exiv2/datasets.hpp b/include/exiv2/datasets.hpp index 18a4bc38..ade488cf 100644 --- a/include/exiv2/datasets.hpp +++ b/include/exiv2/datasets.hpp @@ -369,6 +369,11 @@ namespace Exiv2 { typedef std::set::const_iterator StringSet_i ; typedef std::vector StringVector ,*StringVector_p; typedef std::vector::const_iterator StringVector_i; + typedef std::vector Uint32Vector ,*Uint32Vector_p; + typedef std::vector::const_iterator Uint32Vector_i; + typedef std::vector Uint32Vector ,*Uint32Vector_p; + typedef std::vector::const_iterator Uint32Vector_i; + // ***************************************************************************** // free functions diff --git a/include/exiv2/image.hpp b/include/exiv2/image.hpp index eb87d9b3..8530d5cf 100644 --- a/include/exiv2/image.hpp +++ b/include/exiv2/image.hpp @@ -74,7 +74,9 @@ namespace Exiv2 { /*! @brief Options for printStructure */ - typedef enum { kpsNone, kpsBasic, kpsXMP, kpsRecursive, kpsIccProfile } PrintStructureOption; + typedef enum { kpsNone, kpsBasic, kpsXMP, kpsRecursive + , kpsIccProfile , kpsIptcErase + } PrintStructureOption; /*! @brief Abstract base class defining the interface for an image. This is diff --git a/src/actions.cpp b/src/actions.cpp index 41958470..e1d0d38e 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -949,6 +949,10 @@ namespace Action { if (0 == rc && Params::instance().target_ & Params::ctXmp) { rc = eraseXmpData(image.get()); } + if (0 == rc && Params::instance().target_ & Params::ctIptcRaw) { + rc = printStructure(std::cout,Exiv2::kpsIptcErase); + } + if (0 == rc) { image->writeMetadata(); } @@ -965,6 +969,19 @@ namespace Action { return 1; } // Erase::run + int Erase::printStructure(std::ostream& out, Exiv2::PrintStructureOption option) + { + 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->printStructure(out,option); + return 0; + } + int Erase::eraseThumbnail(Exiv2::Image* image) const { Exiv2::ExifThumb exifThumb(image->exifData()); @@ -1033,25 +1050,29 @@ namespace Action { int Extract::run(const std::string& path) try { path_ = path; - int rc = 0; - if (Params::instance().target_ & Params::ctThumb) { + int rc = 0; + + if (!rc && Params::instance().target_ & Params::ctThumb) { rc = writeThumbnail(); } - if (Params::instance().target_ & Params::ctXmpSidecar) { - std::string xmpPath = newFilePath(path_, ".xmp"); + if (!rc && Params::instance().target_ & Params::ctXmpSidecar) { + std::string xmpPath = Params::instance().target_ & Params::ctStdInOut + ? "-" : newFilePath(path_, ".xmp"); if (dontOverwrite(xmpPath)) return 0; rc = metacopy(path_, xmpPath, Exiv2::ImageType::xmp, false); } - if (Params::instance().target_ & Params::ctPreview) { + if (!rc && Params::instance().target_ & Params::ctPreview) { rc = writePreviews(); } - if (Params::instance().target_ & Params::ctIccProfile) { + if (!rc && Params::instance().target_ & Params::ctIccProfile) { rc = writeIccProfile(); } - if ( !(Params::instance().target_ & Params::ctXmpSidecar) + if (!rc + && !(Params::instance().target_ & Params::ctXmpSidecar) && !(Params::instance().target_ & Params::ctThumb) && !(Params::instance().target_ & Params::ctPreview)) { - std::string exvPath = newFilePath(path_, ".exv"); + std::string exvPath = Params::instance().target_ & Params::ctStdInOut + ? "-" : newFilePath(path_, ".exv"); if (dontOverwrite(exvPath)) return 0; rc = metacopy(path_, exvPath, Exiv2::ImageType::exv, false); } @@ -1220,7 +1241,8 @@ namespace Action { && ( Params::instance().target_ & Params::ctExif || Params::instance().target_ & Params::ctIptc || Params::instance().target_ & Params::ctComment - || Params::instance().target_ & Params::ctXmp)) { + || Params::instance().target_ & Params::ctXmp + || Params::instance().target_ & Params::ctXmpRaw)) { std::string suffix = Params::instance().suffix_; if (suffix.empty()) suffix = ".exv"; if (Params::instance().target_ & Params::ctXmpSidecar) suffix = ".xmp"; @@ -1915,15 +1937,18 @@ namespace { } // tm2Str int metacopy(const std::string& source, - const std::string& target, + const std::string& tgt, int targetType, bool preserve) { + // read the source metadata + int rc = -1 ; if (!Exiv2::fileExists(source, true)) { std::cerr << source << ": " << _("Failed to open the file\n"); - return -1; + return rc; } + Exiv2::Image::AutoPtr sourceImage = Exiv2::ImageFactory::open(source); assert(sourceImage.get() != 0); sourceImage->readMetadata(); @@ -1931,6 +1956,12 @@ namespace { // Apply any modification commands to the source image on-the-fly Action::Modify::applyCommands(sourceImage.get()); + // Open or create the target file + std::string target = tgt; + bool bTemporary = target == "-"; + if ( bTemporary ) { + target = Exiv2::FileIo::temporaryPath(); + } Exiv2::Image::AutoPtr targetImage; if (Exiv2::fileExists(target)) { targetImage = Exiv2::ImageFactory::open(target); @@ -1941,6 +1972,8 @@ namespace { targetImage = Exiv2::ImageFactory::create(targetType, target); assert(targetImage.get() != 0); } + + // Copy each type of metadata if ( Params::instance().target_ & Params::ctExif && !sourceImage->exifData().empty()) { if (Params::instance().verbose_) { @@ -1957,24 +1990,28 @@ namespace { } targetImage->setIptcData(sourceImage->iptcData()); } - if ( Params::instance().target_ & Params::ctXmp + if ( Params::instance().target_ & (Params::ctXmp|Params::ctXmpRaw) && !sourceImage->xmpData().empty()) { if (Params::instance().verbose_) { std::cout << _("Writing XMP data from") << " " << source << " " << _("to") << " " << target << std::endl; } - // #1148 use XMP packet if there are no XMP modification commands - if ( Params::instance().modifyCmds_.size() == 0 - && Params::instance().target_ == (Params::ctXmp | Params::ctXmpSidecar) // option -eXx - ) { + // #1148 use Raw XMP packet if there are no XMP modification commands + int tRawSidecar = Params::ctXmpSidecar | Params::ctXmpRaw; // option -eXX + // printTarget("in metacopy",Params::instance().target_,true); + if( Params::instance().modifyCmds_.size() == 0 + && (Params::instance().target_ & tRawSidecar) == tRawSidecar + ){ + // std::cout << "short cut" << std::endl; // http://www.cplusplus.com/doc/tutorial/files/ std::ofstream os; os.open(target.c_str()); sourceImage->printStructure(os,Exiv2::kpsXMP); os.close(); - return 0; + rc = 0; } else { + // std::cout << "long cut" << std::endl; targetImage->setXmpData(sourceImage->xmpData()); } } @@ -1986,16 +2023,35 @@ namespace { } targetImage->setComment(sourceImage->comment()); } - try { + if ( rc < 0 ) try { targetImage->writeMetadata(); + rc=0; } catch (const Exiv2::AnyError& e) { std::cerr << target << ": " << _("Could not write metadata to file") << ": " << e << "\n"; - return 1; + rc=1; + } + + // if we used a temporary target, copy it to stdout + if ( rc == 0 && bTemporary ) { + FILE* f = ::fopen(target.c_str(),"rb") ; + if ( f ) { + char buffer[8*1024]; + int n = 1 ; + while ( !feof(f) && n > 0) { + n=fread(buffer,1,sizeof buffer,f); + fwrite(buffer,1,n,stdout); + } + fclose(f); + } + std::cout << std::endl; } - return 0; + // delete temporary target + if ( bTemporary ) std::remove(target.c_str()); + + return rc; } // metacopy // Defined outside of the function so that Exiv2::find() can see it @@ -2113,6 +2169,8 @@ namespace { int dontOverwrite(const std::string& path) { + if ( path == "-" ) return 0; + if (!Params::instance().force_ && Exiv2::fileExists(path)) { std::cout << Params::instance().progname() << ": " << _("Overwrite") << " `" << path << "'? "; diff --git a/src/actions.hpp b/src/actions.hpp index be2cab2c..cfc1d3a2 100644 --- a/src/actions.hpp +++ b/src/actions.hpp @@ -274,6 +274,11 @@ namespace Action { @brief Erase XMP packet from the file. */ int eraseXmpData(Exiv2::Image* image) const; + /*! + @brief Print image Structure information (used by ctIptcRaw/kpsIptcErase) + */ + int printStructure(std::ostream& out, Exiv2::PrintStructureOption option); + private: virtual Erase* clone_() const; diff --git a/src/exiv2.cpp b/src/exiv2.cpp index 22f72dd1..d1d0fc3b 100644 --- a/src/exiv2.cpp +++ b/src/exiv2.cpp @@ -238,6 +238,25 @@ void Params::usage(std::ostream& os) const << _("Manipulate the Exif metadata of images.\n"); } +std::string Params::printTarget(std::string before,int target,bool bPrint,std::ostream& out) +{ + std::string t; + if ( target & Params::ctExif ) t+= 'e'; + if ( target & Params::ctXmpSidecar ) t+= 'X'; + if ( target & Params::ctXmpRaw ) t+= target & Params::ctXmpSidecar ? 'X' : 'R' ; + if ( target & Params::ctIptc ) t+= 'i'; + if ( target & Params::ctIccProfile ) t+= 'C'; + if ( target & Params::ctIptcRaw ) t+= 'I'; + if ( target & Params::ctXmp ) t+= 'x'; + if ( target & Params::ctComment ) t+= 'c'; + if ( target & Params::ctThumb ) t+= 't'; + if ( target & Params::ctPreview ) t+= 'p'; + if ( target & Params::ctStdInOut ) t+= '-'; + + if ( bPrint ) out << before << " :" << t << std::endl; + return t; +} + void Params::help(std::ostream& os) const { usage(os); @@ -1028,7 +1047,7 @@ namespace { int parseCommonTargets(const std::string& optarg, const std::string& action) { - int rc = 0; + int rc = 0; int target = 0; for (size_t i = 0; rc == 0 && i < optarg.size(); ++i) { switch (optarg[i]) { @@ -1038,14 +1057,25 @@ namespace { case 'c': target |= Params::ctComment; break; case 't': target |= Params::ctThumb; break; case 'C': target |= Params::ctIccProfile; break; + case 'I': target |= Params::ctIptcRaw;break; + case '-': target |= Params::ctStdInOut;break; case 'a': target |= Params::ctExif | Params::ctIptc | Params::ctComment | Params::ctXmp; break; case 'X': - target |= Params::ctXmpSidecar; - if (optarg == "X") target |= Params::ctExif | Params::ctIptc | Params::ctXmp; - break; + Params::printTarget("X before",target); + target |= Params::ctXmpSidecar|Params::ctExif | Params::ctIptc | Params::ctXmp ; // -eX + Params::printTarget("X after1",target); + + if ( i ) { // -eXX + target |= Params::ctXmpRaw ; + Params::printTarget("X after2",target); + target ^= Params::ctExif|Params::ctIptc|Params::ctXmp ; // turn off those bits + } + Params::printTarget("X ending",target,false); + break; + case 'p': { if (strcmp(action.c_str(), "extract") == 0) { diff --git a/src/exiv2app.hpp b/src/exiv2app.hpp index 7dc9fef2..a629c33b 100644 --- a/src/exiv2app.hpp +++ b/src/exiv2app.hpp @@ -171,14 +171,17 @@ public: //! Enumerates common targets, bitmap enum CommonTarget { - ctExif = 1, - ctIptc = 2, - ctComment = 4, - ctThumb = 8, - ctXmp = 16, - ctXmpSidecar = 32, - ctPreview = 64, - ctIccProfile =128 + ctExif = 1, + ctIptc = 2, + ctComment = 4, + ctThumb = 8, + ctXmp = 16, + ctXmpSidecar = 32, + ctPreview = 64, + ctIccProfile = 128, + ctXmpRaw = 256, + ctStdInOut = 512, + ctIptcRaw =1024 }; //! Enumerates the policies to handle existing files in rename action @@ -314,6 +317,10 @@ public: //! Print version information to an output stream. void version(bool verbose =false, std::ostream& os =std::cout) const; + + //! Print target_ + static std::string printTarget(std::string before,int target,bool bPrint=false,std::ostream& os=std::cout); + }; // class Params #endif // #ifndef EXIV2APP_HPP_ diff --git a/src/jpgimage.cpp b/src/jpgimage.cpp index 493ecd64..a7979ffa 100644 --- a/src/jpgimage.cpp +++ b/src/jpgimage.cpp @@ -538,7 +538,10 @@ namespace Exiv2 { throw Error(15); } - if ( option == kpsBasic || option == kpsXMP || option == kpsIccProfile || option == kpsRecursive) { + bool bPrint = option==kpsBasic || option==kpsRecursive; + Exiv2::Uint32Vector iptcDataSegs; + + if ( bPrint || option == kpsXMP || option == kpsIccProfile || option == kpsIptcErase ) { // nmonic for markers std::string nm[256] ; @@ -576,13 +579,13 @@ namespace Exiv2 { bool first= true; while (!done) { // print marker bytes - if ( first && (option == kpsBasic||option==kpsRecursive) ) { + if ( first && bPrint ) { out << "STRUCTURE OF JPEG FILE: " << io_->path() << std::endl; out << " address | marker | length | data" << std::endl ; REPORT_MARKER; } first = false; - bool bLF = option == kpsBasic||option == kpsRecursive; + bool bLF = bPrint; // Read size and signature std::memset(buf.pData_, 0x0, buf.size_); @@ -602,17 +605,22 @@ namespace Exiv2 { ){ size = getUShort(buf.pData_, bigEndian); } - if ( option == kpsBasic||option==kpsRecursive ) out << Internal::stringFormat(" | %7d ", size); + if ( bPrint ) out << Internal::stringFormat(" | %7d ", size); // only print the signature for appn if (marker >= app0_ && marker <= (app0_ | 0x0F)) { - char http[5]; - http[4]=0; - memcpy(http,buf.pData_+2,4); - - if ( option == kpsXMP && std::strcmp(http,"http") == 0 ) { + // http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart3.pdf p75 + const char* signature = (const char*) buf.pData_+2; + + // 728 rmills@rmillsmbp:~/gnu/exiv2/ttt $ exiv2 -pS test/data/exiv2-bug922.jpg + // STRUCTURE OF JPEG FILE: test/data/exiv2-bug922.jpg + // address | marker | length | data + // 2 | 0xd8 SOI | 0 + // 4 | 0xe1 APP1 | 911 | Exif..MM.*.......%.........#.... + // 917 | 0xe1 APP1 | 870 | http://ns.adobe.com/xap/1.0/. 0 ) { io_->seek(-bufRead , BasicIo::cur); byte* xmp = new byte[size+1]; @@ -622,7 +630,10 @@ namespace Exiv2 { // http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart3.pdf // if we find HasExtendedXMP, set the flag and ignore this block // the first extended block is a copy of the Standard block. - // a robust implementation enables extended blocks to be out of sequence + // a robust implementation allows extended blocks to be out of sequence + // we could implement out of sequence with a dictionary of sequence/offset + // and dumping the XMP in a post read operation similar to kpsIptcErase + // for the moment, dumping 'on the fly' is working fine if ( ! bExtXMP ) { while (xmp[start]) start++; start++; if ( ::strstr((char*)xmp+start,"HasExtendedXMP") ) { @@ -634,11 +645,11 @@ namespace Exiv2 { } xmp[size]=0; - out << xmp + start; // this is all we need to output without the blank line dance. + out << xmp + start; delete [] xmp; bufRead = size; } - } else if ( option == kpsIccProfile && std::strcmp(http,"ICC_") == 0 ) { + } else if ( option == kpsIccProfile && std::strcmp(signature,"ICC_PROFILE") == 0 ) { // extract ICCProfile if ( size > 0 ) { io_->seek(-bufRead , BasicIo::cur); @@ -649,15 +660,22 @@ namespace Exiv2 { bufRead = size; delete [] icc; } - } else if ( option == kpsBasic||option==kpsRecursive ) { + } else if ( option == kpsIptcErase && std::strcmp(signature,"Photoshop 3.0") == 0 ) { + // delete IPTC data segment from JPEG + if ( size > 0 ) { + io_->seek(-bufRead , BasicIo::cur); + iptcDataSegs.push_back(io_->tell()); + iptcDataSegs.push_back(size); + } + } else if ( bPrint ) { out << "| " << Internal::binaryToString(buf,size>32?32:size,size>0?2:0); } // for MPF: http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/MPF.html // for FLIR: http://owl.phy.queensu.ca/~phil/exiftool/TagNames/FLIR.html - bool bFlir = option == kpsRecursive && marker == (app0_+1) && std::strcmp(http,"FLIR")==0; - bool bExif = option == kpsRecursive && marker == (app0_+1) && std::strcmp(http,"Exif")==0; - bool bMPF = option == kpsRecursive && marker == (app0_+2) && std::strcmp(http,"MPF")==0; + bool bFlir = option == kpsRecursive && marker == (app0_+1) && std::strcmp(signature,"FLIR")==0; + bool bExif = option == kpsRecursive && marker == (app0_+1) && std::strcmp(signature,"Exif")==0; + bool bMPF = option == kpsRecursive && marker == (app0_+2) && std::strcmp(signature,"MPF")==0; if( bFlir || bExif || bMPF ) { // extract Exif data block which is tiff formatted if ( size > 0 ) { @@ -670,7 +688,7 @@ namespace Exiv2 { // copy the data to memory io_->seek(-bufRead , BasicIo::cur); io_->read(exif,size); - uint32_t start = std::strcmp(http,"Exif")==0 ? 8 : 6; + uint32_t start = std::strcmp(signature,"Exif")==0 ? 8 : 6; uint32_t max = (uint32_t) size -1; // is this an fff block? @@ -730,6 +748,14 @@ namespace Exiv2 { } } } + if ( option == kpsIptcErase ) { + std::cout << "iptc data blocks: " << (iptcDataSegs.size() ? "FOUND" : "none") << std::endl; + uint32_t toggle = 0 ; + for ( Uint32Vector_i it = iptcDataSegs.begin(); it != iptcDataSegs.end() ; it++ ) { + std::cout << *it ; + if ( toggle++ % 2 ) std::cout << std::endl; else std::cout << ' ' ; + } + } } void JpegBase::writeMetadata() diff --git a/src/tiffimage.cpp b/src/tiffimage.cpp index 324266ad..d41934b4 100644 --- a/src/tiffimage.cpp +++ b/src/tiffimage.cpp @@ -480,22 +480,24 @@ namespace Exiv2 { // buffer const size_t dirSize = 32; DataBuf dir(dirSize); + bool bPrint = option == kpsBasic || option == kpsRecursive; + do { // Read top of directory io.seek(start,BasicIo::beg); io.read(dir.pData_, 2); uint16_t dirLength = byteSwap2(dir,0,bSwap); - bool tooBig = dirLength > 200 ; - bool bPrint = option == kpsBasic || option == kpsRecursive; + bool tooBig = dirLength > 500; if ( bFirst && bPrint ) { out << indent(depth) << Internal::stringFormat("STRUCTURE OF TIFF FILE (%c%c): ",c,c) << io.path() << std::endl; if ( tooBig ) out << indent(depth) << "dirLength = " << dirLength << std::endl; } + if (tooBig) break; // Read the dictionary - for ( int i = 0 ; !tooBig && i < dirLength ; i ++ ) { + for ( int i = 0 ; i < dirLength ; i ++ ) { if ( bFirst && bPrint ) { out << indent(depth) << " address | tag | " @@ -594,7 +596,7 @@ namespace Exiv2 { out.flush(); } while (start) ; - if ( option == kpsBasic || option == kpsRecursive ) { + if ( bPrint ) { out << indent(depth) << "END " << io.path() << std::endl; } depth--;