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 <ctime>
#include <cmath>
#include <cassert>
#include <sys/types.h> // for stat()
#include <sys/stat.h> // for stat()
#ifdef HAVE_UNISTD_H
@ -131,6 +132,7 @@ namespace Action {
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)
@ -917,6 +919,129 @@ namespace Action {
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)
try {
adjustment_ = Params::instance().adjustment_;

@ -37,6 +37,10 @@
#include <string>
#include <map>
#include "exiv2.hpp"
#include "exif.hpp"
#include "iptc.hpp"
// *****************************************************************************
// class declarations
@ -53,7 +57,7 @@ namespace Exiv2 {
namespace Action {
//! Enumerates all tasks
enum TaskType { none, adjust, print, rename, erase, extract, insert };
enum TaskType { none, adjust, print, rename, erase, extract, insert, modify };
// *****************************************************************************
// class definitions
@ -289,6 +293,31 @@ namespace Action {
}; // 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
#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 <iostream>
#include <fstream>
#include <iomanip>
#include <cstring>
#include <cassert>
@ -54,6 +55,17 @@ EXIV2_RCSID("@(#) $Id$");
// local declarations
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
// in seconds if successful, else returns false.
bool parseTime(const std::string& ts, long& time);
@ -66,6 +78,25 @@ namespace {
*/
int parseCommonTargets(const std::string& optarg,
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
{
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. "
<< "There is NO \nwarranty; not even for MERCHANTABILITY or FITNESS FOR "
<< "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"
<< " 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\n"
<< "\nOptions:\n"
<< " -h Display this help 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"
<< " -r fmt Filename format for the `rename' action. The format string\n"
<< " 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
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;
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 ':':
std::cerr << progname() << ": Option -" << static_cast<char>(optopt)
<< " requires an argument\n";
@ -404,6 +456,15 @@ int Params::nonoption(const std::string& argv)
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;
@ -430,10 +491,22 @@ int Params::getopt(int argc, char* const argv[])
<< ": Adjust action requires option -a time\n";
rc = 1;
}
if (action_ == Action::modify && cmdFile_.empty()) {
std::cerr << progname()
<< ": Modify action requires option -m file\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) {
if (!parseCommands(modifyCmds_, cmdFile_)) {
std::cerr << progname() << ": Error parsing -m option argument `"
<< cmdFile_ << "'\n";
rc = 1;
}
}
return rc;
} // Params::getopt
@ -505,4 +578,147 @@ namespace {
return rc ? rc : target;
} // 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
#include "utils.hpp"
#include "types.hpp"
// + standard includes
#include <string>
@ -40,6 +41,33 @@
// *****************************************************************************
// 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.
@ -103,6 +131,8 @@ public:
long adjustment_; //!< Adjustment in seconds.
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.
typedef std::vector<std::string> Files;
@ -114,7 +144,7 @@ private:
@brief Default constructor. Note that optstring_ is initialized here.
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),
version_(false),
verbose_(false),

@ -58,14 +58,16 @@ namespace Exiv2 {
TypeInfoTable(unsignedShort, "Short", 2),
TypeInfoTable(unsignedLong, "Long", 4),
TypeInfoTable(unsignedRational, "Rational", 8),
TypeInfoTable(invalid6, "Invalid (6)", 1),
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(time, "Time", 11),
// End of list marker
TypeInfoTable(lastTypeId, "(Unknown)", 0)
};
const char* TypeInfo::typeName(TypeId typeId)
@ -73,6 +75,15 @@ namespace Exiv2 {
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_;

@ -46,6 +46,7 @@
#include <iosfwd>
#include <utility>
#include <sstream>
#include <cstdio>
#ifdef HAVE_STDINT_H
# include <stdint.h>
#endif
@ -113,6 +114,8 @@ namespace Exiv2 {
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);

Loading…
Cancel
Save