Added modify action to exiv2 tool. Implements feature #406

v0.27.3
Andreas Huggel 21 years ago
parent 01c09f503e
commit 3a6b642c6a

@ -59,6 +59,7 @@ EXIV2_RCSID("@(#) $Id$");
#include <cstdio> #include <cstdio>
#include <ctime> #include <ctime>
#include <cmath> #include <cmath>
#include <cassert>
#include <sys/types.h> // for stat() #include <sys/types.h> // for stat()
#include <sys/stat.h> // for stat() #include <sys/stat.h> // for stat()
#ifdef HAVE_UNISTD_H #ifdef HAVE_UNISTD_H
@ -131,6 +132,7 @@ namespace Action {
registerTask(erase, Task::AutoPtr(new Erase)); registerTask(erase, Task::AutoPtr(new Erase));
registerTask(extract, Task::AutoPtr(new Extract)); registerTask(extract, Task::AutoPtr(new Extract));
registerTask(insert, Task::AutoPtr(new Insert)); registerTask(insert, Task::AutoPtr(new Insert));
registerTask(modify, Task::AutoPtr(new Modify));
} // TaskFactory c'tor } // TaskFactory c'tor
Task::AutoPtr TaskFactory::create(TaskType type) Task::AutoPtr TaskFactory::create(TaskType type)
@ -917,6 +919,129 @@ namespace Action {
return new Insert(*this); return new Insert(*this);
} }
int Modify::run(const std::string& path)
try {
if (!Util::fileExists(path, true)) {
std::cerr << path
<< ": Failed to open the file\n";
return -1;
}
// Read both exif and iptc metadata (ignore return code)
exifData_.read(path);
iptcData_.read(path);
// 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
int rc = exifData_.write(path);
if (rc) {
std::cerr << Exiv2::ExifData::strError(rc, path) << "\n";
}
rc = iptcData_.write(path);
if (rc) {
std::cerr << Exiv2::IptcData::strError(rc, path) << "\n";
}
return rc;
}
catch(const Exiv2::Error& e)
{
std::cerr << "Exif 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) {
exifData_.add(Exiv2::ExifKey(modifyCmd.key_), value.get());
}
if (modifyCmd.metadataId_ == iptc) {
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::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;
}
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) int Adjust::run(const std::string& path)
try { try {
adjustment_ = Params::instance().adjustment_; adjustment_ = Params::instance().adjustment_;

@ -37,6 +37,10 @@
#include <string> #include <string>
#include <map> #include <map>
#include "exiv2.hpp"
#include "exif.hpp"
#include "iptc.hpp"
// ***************************************************************************** // *****************************************************************************
// class declarations // class declarations
@ -53,7 +57,7 @@ namespace Exiv2 {
namespace Action { namespace Action {
//! Enumerates all tasks //! Enumerates all tasks
enum TaskType { none, adjust, print, rename, erase, extract, insert }; enum TaskType { none, adjust, print, rename, erase, extract, insert, modify };
// ***************************************************************************** // *****************************************************************************
// class definitions // class definitions
@ -289,6 +293,31 @@ namespace Action {
}; // class Insert }; // 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;
private:
virtual Modify* clone_() const;
//! 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::ExifData exifData_; //!< Exif metadata
Exiv2::IptcData iptcData_; //!< Iptc metadata
}; // class Modify
} // namespace Action } // namespace Action
#endif // #ifndef ACTIONS_HPP_ #endif // #ifndef ACTIONS_HPP_

@ -0,0 +1,30 @@
# Sample Exiv2 command file
# Run exiv2 -m cmd.txt file ...
# to apply the commands to each file.
#
# Command file format
# Empty lines and lines starting with # are ignored
# Each remaining line is a command. The format for command lines is
# <cmd> <key> [[<type>] <value>]
# cmd = set|add|del
# set will set the value of an existing tag of the given key or add a tag
# add will add a tag (unless the key is a non-repeatable Iptc key)
# del will delete a tag
# key = Exiv2 Exif or Iptc key
# type = Byte|Ascii|Short|Long|Rational|Undefined|SShort|SLong|SRational for Exif
# String|Date|Time|Short|Undefined for Iptc
# A default type is used if none is explicitely given. The default for Exif
# keys is Ascii, that for Iptc keys is determined based on the key itself.
# value
# The remaining text on the line is the value. It can optionally be enclosed in
# double quotes ("value")
add Iptc.Application2.Credit String "mee too! (1)"
add Iptc.Application2.Credit mee too! (2)
del Iptc.Application2.Headline
add Exif.Image.WhitePoint Short 32 12 4 5 6
set Exif.Image.DateTime Ascii "Zwanzig nach fuenf"
set Exif.Image.Artist Ascii nobody
set Exif.Image.Artist "Vincent van Gogh"

@ -46,6 +46,7 @@ EXIV2_RCSID("@(#) $Id$");
#include <string> #include <string>
#include <iostream> #include <iostream>
#include <fstream>
#include <iomanip> #include <iomanip>
#include <cstring> #include <cstring>
#include <cassert> #include <cassert>
@ -54,6 +55,17 @@ EXIV2_RCSID("@(#) $Id$");
// local declarations // local declarations
namespace { namespace {
//! List of all command itentifiers 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 // Evaluate [-]HH[:MM[:SS]], returns true and sets time to the value
// in seconds if successful, else returns false. // in seconds if successful, else returns false.
bool parseTime(const std::string& ts, long& time); bool parseTime(const std::string& ts, long& time);
@ -66,6 +78,25 @@ namespace {
*/ */
int parseCommonTargets(const std::string& optarg, int parseCommonTargets(const std::string& optarg,
const std::string& action); const std::string& action);
/*!
@brief Parse metadata modification commands from a file
@param modifyCmds Reference to a structure to store the parsed commands
@param filename Name of the command file
*/
bool parseCommands(ModifyCmds& modifyCmds,
const std::string& filename);
/*!
@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);
} }
// ***************************************************************************** // *****************************************************************************
@ -123,7 +154,7 @@ Params& Params::instance()
void Params::version(std::ostream& os) const void Params::version(std::ostream& os) const
{ {
os << PACKAGE_STRING << ", " os << PACKAGE_STRING << ", "
<< "Copyright (C) 2004 Andreas Huggel.\n\n" << "Copyright (C) 2004, 2005 Andreas Huggel.\n\n"
<< "This is free software; see the source for copying conditions. " << "This is free software; see the source for copying conditions. "
<< "There is NO \nwarranty; not even for MERCHANTABILITY or FITNESS FOR " << "There is NO \nwarranty; not even for MERCHANTABILITY or FITNESS FOR "
<< "A PARTICULAR PURPOSE.\n"; << "A PARTICULAR PURPOSE.\n";
@ -148,6 +179,8 @@ void Params::help(std::ostream& os) const
<< " ex | extract Extract metadata to *.exv and thumbnail image files.\n" << " ex | extract Extract metadata to *.exv and thumbnail image files.\n"
<< " mv | rename Rename files according to the Exif create timestamp.\n" << " mv | rename Rename files according to the Exif create timestamp.\n"
<< " The filename format can be set with -r format.\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\n"
<< "\nOptions:\n" << "\nOptions:\n"
<< " -h Display this help and exit.\n" << " -h Display this help and exit.\n"
<< " -V Show the program version and exit.\n" << " -V Show the program version and exit.\n"
@ -175,7 +208,9 @@ void Params::help(std::ostream& os) const
<< " are the same as those for the -d option.\n" << " are the same as those for the -d option.\n"
<< " -r fmt Filename format for the `rename' action. The format string\n" << " -r fmt Filename format for the `rename' action. The format string\n"
<< " follows strftime(3). Default filename format is " << " follows strftime(3). Default filename format is "
<< format_ << ".\n\n"; << format_ << ".\n"
<< " -m file Command file for the modify action. The format for the commands\n"
<< " set|add|del <key> [[<Type>] <value>].\n\n";
} // Params::help } // Params::help
int Params::option(int opt, const std::string& optarg, int optopt) int Params::option(int opt, const std::string& optarg, int optopt)
@ -323,6 +358,23 @@ int Params::option(int opt, const std::string& optarg, int optopt)
break; break;
} }
break; break;
case 'm':
switch (action_) {
case Action::none:
action_ = Action::modify;
cmdFile_ = optarg; // parse the file later
break;
case Action::modify:
std::cerr << progname()
<< ": Ignoring surplus option -m " << optarg << "\n";
break;
default:
std::cerr << progname()
<< ": Option -m is not compatible with a previous option\n";
rc = 1;
break;
}
break;
case ':': case ':':
std::cerr << progname() << ": Option -" << static_cast<char>(optopt) std::cerr << progname() << ": Option -" << static_cast<char>(optopt)
<< " requires an argument\n"; << " requires an argument\n";
@ -404,6 +456,15 @@ int Params::nonoption(const std::string& argv)
action = true; action = true;
action_ = Action::rename; 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 (action_ == Action::none) {
// if everything else fails, assume print as the default action // if everything else fails, assume print as the default action
action_ = Action::print; action_ = Action::print;
@ -430,10 +491,22 @@ int Params::getopt(int argc, char* const argv[])
<< ": Adjust action requires option -a time\n"; << ": Adjust action requires option -a time\n";
rc = 1; rc = 1;
} }
if (action_ == Action::modify && cmdFile_.empty()) {
std::cerr << progname()
<< ": Modify action requires option -m file\n";
rc = 1;
}
if (0 == files_.size()) { if (0 == files_.size()) {
std::cerr << progname() << ": At least one file is required\n"; std::cerr << progname() << ": At least one file is required\n";
rc = 1; rc = 1;
} }
if (rc == 0 && action_ == Action::modify) {
if (!parseCommands(modifyCmds_, cmdFile_)) {
std::cerr << progname() << ": Error parsing -m option argument `"
<< cmdFile_ << "'\n";
rc = 1;
}
}
return rc; return rc;
} // Params::getopt } // Params::getopt
@ -505,4 +578,147 @@ namespace {
return rc ? rc : target; return rc ? rc : target;
} // parseCommonTargets } // parseCommonTargets
bool parseCommands(ModifyCmds& modifyCmds,
const std::string& 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);
}
}
return true;
}
catch (const Exiv2::Error& error) {
std::cerr << filename << ", " << error << "\n";
return false;
}
} // parseCommands
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("line " + Exiv2::toString(num)
+ ": Invalid command line");
}
std::string cmd(line.substr(cmdStart, cmdEnd-cmdStart));
CmdId cmdId = commandId(cmd);
if (cmdId == invalidCmdId) {
throw Exiv2::Error("line " + 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::Error&) {}
if (metadataId == invalidMetadataId) {
try {
Exiv2::ExifKey exifKey(key);
metadataId = exif;
defaultType = Exiv2::asciiString;
}
catch (const Exiv2::Error&) {}
}
if (metadataId == invalidMetadataId) {
throw Exiv2::Error("line " + Exiv2::toString(num)
+ ": Invalid key `" + key + "'");
}
std::string value;
Exiv2::TypeId type = Exiv2::invalidTypeId;
bool explicitType = true;
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
|| typeEnd == std::string::npos
|| valStart == std::string::npos) {
throw Exiv2::Error("line " + Exiv2::toString(num)
+ ": Invalid command line");
}
std::string typeStr(line.substr(typeStart, typeEnd-typeStart));
type = Exiv2::TypeInfo::typeId(typeStr);
if (type != Exiv2::invalidTypeId) {
valStart = line.find_first_not_of(delim, typeEnd+1);
if (valStart == std::string::npos) {
throw Exiv2::Error("line " + Exiv2::toString(num)
+ ": Invalid command line");
}
}
else {
type = defaultType;
explicitType = false;
}
if (type == Exiv2::invalidTypeId) {
throw Exiv2::Error("line " + Exiv2::toString(num)
+ ": Invalid type");
}
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("line " + 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_;
}
} }

@ -32,6 +32,7 @@
// ***************************************************************************** // *****************************************************************************
// included header files // included header files
#include "utils.hpp" #include "utils.hpp"
#include "types.hpp"
// + standard includes // + standard includes
#include <string> #include <string>
@ -40,6 +41,33 @@
// ***************************************************************************** // *****************************************************************************
// class definitions // 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. @brief Implements the command line handling for the program.
@ -103,6 +131,8 @@ public:
long adjustment_; //!< Adjustment in seconds. long adjustment_; //!< Adjustment in seconds.
std::string format_; //!< Filename format (-r option arg). std::string format_; //!< Filename format (-r option arg).
std::string cmdFile_; //!< Name of the modification command file
ModifyCmds modifyCmds_; //!< Parsed modification commands
//! Container to store filenames. //! Container to store filenames.
typedef std::vector<std::string> Files; typedef std::vector<std::string> Files;
@ -114,7 +144,7 @@ private:
@brief Default constructor. Note that optstring_ is initialized here. @brief Default constructor. Note that optstring_ is initialized here.
The c'tor is private to force instantiation through instance(). The c'tor is private to force instantiation through instance().
*/ */
Params() : optstring_(":hVvfa:r:p:d:e:i:"), Params() : optstring_(":hVvfa:r:p:d:e:i:m:"),
help_(false), help_(false),
version_(false), version_(false),
verbose_(false), verbose_(false),

@ -58,14 +58,16 @@ namespace Exiv2 {
TypeInfoTable(unsignedShort, "Short", 2), TypeInfoTable(unsignedShort, "Short", 2),
TypeInfoTable(unsignedLong, "Long", 4), TypeInfoTable(unsignedLong, "Long", 4),
TypeInfoTable(unsignedRational, "Rational", 8), TypeInfoTable(unsignedRational, "Rational", 8),
TypeInfoTable(invalid6, "Invalid (6)", 1), TypeInfoTable(invalid6, "Invalid(6)", 1),
TypeInfoTable(undefined, "Undefined", 1), TypeInfoTable(undefined, "Undefined", 1),
TypeInfoTable(signedShort, "SShort", 2), TypeInfoTable(signedShort, "SShort", 2),
TypeInfoTable(signedLong, "SLong", 4), TypeInfoTable(signedLong, "SLong", 4),
TypeInfoTable(signedRational, "SRational", 8), TypeInfoTable(signedRational, "SRational", 8),
TypeInfoTable(string, "String", 1), TypeInfoTable(string, "String", 1),
TypeInfoTable(date, "Date", 8), TypeInfoTable(date, "Date", 8),
TypeInfoTable(time, "Time", 11) TypeInfoTable(time, "Time", 11),
// End of list marker
TypeInfoTable(lastTypeId, "(Unknown)", 0)
}; };
const char* TypeInfo::typeName(TypeId typeId) const char* TypeInfo::typeName(TypeId typeId)
@ -73,6 +75,15 @@ namespace Exiv2 {
return typeInfoTable_[ typeId < lastTypeId ? typeId : 0 ].name_; 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) long TypeInfo::typeSize(TypeId typeId)
{ {
return typeInfoTable_[ typeId < lastTypeId ? typeId : 0 ].size_; return typeInfoTable_[ typeId < lastTypeId ? typeId : 0 ].size_;

@ -46,6 +46,7 @@
#include <iosfwd> #include <iosfwd>
#include <utility> #include <utility>
#include <sstream> #include <sstream>
#include <cstdio>
#ifdef HAVE_STDINT_H #ifdef HAVE_STDINT_H
# include <stdint.h> # include <stdint.h>
#endif #endif
@ -113,6 +114,8 @@ namespace Exiv2 {
public: public:
//! Return the name of the type //! Return the name of the type
static const char* typeName(TypeId typeId); 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 //! Return the size in bytes of one element of this type
static long typeSize(TypeId typeId); static long typeSize(TypeId typeId);

Loading…
Cancel
Save