You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

957 lines
34 KiB
C++

// ***************************************************************** -*- C++ -*-
/*
* Copyright (C) 2004-2007 Andreas Huggel <ahuggel@gmx.net>
*
* This program is part of the Exiv2 distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
*/
/*
Abstract: Command line program to display and manipulate image %Exif data
File: exiv2.cpp
Version: $Rev$
Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
History: 10-Dec-03, ahu: created
*/
// *****************************************************************************
#include "rcsid.hpp"
EXIV2_RCSID("@(#) $Id$")
// *****************************************************************************
// included header files
#ifdef _MSC_VER
# include "exv_msvc.h"
#else
# include "exv_conf.h"
#endif
#include "exiv2.hpp"
#include "actions.hpp"
#include "utils.hpp"
#include "i18n.h" // NLS support.
#include <string>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstring>
#include <cassert>
// *****************************************************************************
// local declarations
namespace {
//! List of all command identifiers 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);
/*!
@brief Parse the oparg string into a bitmap of common targets.
@param optarg Option arguments
@param action Action being processed
@return A bitmap of common targets or -1 in case of a parse error
*/
int parseCommonTargets(const std::string& optarg,
const std::string& action);
/*!
@brief Parse metadata modification commands from multiple files
@param modifyCmds Reference to a structure to store the parsed commands
@param cmdFiles Container with the file names
*/
bool parseCmdFiles(ModifyCmds& modifyCmds,
const Params::CmdFiles& cmdFiles);
/*!
@brief Parse metadata modification commands from a container of commands
@param modifyCmds Reference to a structure to store the parsed commands
@param cmdLines Container with the commands
*/
bool parseCmdLines(ModifyCmds& modifyCmds,
const Params::CmdLines& cmdLines);
/*!
@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);
}
// *****************************************************************************
// Main
int main(int argc, char* const argv[])
{
#ifdef EXV_ENABLE_NLS
setlocale(LC_ALL, "");
bindtextdomain(EXV_PACKAGE, EXV_LOCALEDIR);
textdomain(EXV_PACKAGE);
#endif
// Handle command line arguments
Params& params = Params::instance();
if (params.getopt(argc, argv)) {
params.usage();
return 1;
}
if (params.help_) {
params.help();
return 0;
}
if (params.version_) {
params.version();
return 0;
}
// Create the required action class
Action::TaskFactory& taskFactory = Action::TaskFactory::instance();
Action::Task::AutoPtr task
= taskFactory.create(Action::TaskType(params.action_));
assert(task.get());
// Process all files
int n = 1;
int s = static_cast<int>(params.files_.size());
int w = s > 9 ? s > 99 ? 3 : 2 : 1;
Params::Files::const_iterator e = params.files_.end();
for (Params::Files::const_iterator i = params.files_.begin(); i != e; ++i) {
if (params.verbose_) {
std::cout << _("File") << " " << std::setw(w) << n++ << "/" << s << ": "
<< *i << std::endl;
}
task->run(*i);
}
taskFactory.cleanup();
params.cleanup();
return 0;
} // main
// *****************************************************************************
// class Params
Params* Params::instance_ = 0;
Params& Params::instance()
{
if (0 == instance_) {
instance_ = new Params;
}
return *instance_;
}
void Params::cleanup()
{
delete instance_;
instance_ = 0;
}
void Params::version(std::ostream& os) const
{
os << EXV_PACKAGE_STRING << "\n"
<< _("Copyright (C) 2004-2007 Andreas Huggel.\n")
<< "\n"
<< _("This program is free software; you can redistribute it and/or\n"
"modify it under the terms of the GNU General Public License\n"
"as published by the Free Software Foundation; either version 2\n"
"of the License, or (at your option) any later version.\n")
<< "\n"
<< _("This program is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"GNU General Public License for more details.\n")
<< "\n"
<< _("You should have received a copy of the GNU General Public\n"
"License along with this program; if not, write to the Free\n"
"Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n"
"Boston, MA 02110-1301 USA\n");
}
void Params::usage(std::ostream& os) const
{
os << _("Usage:") << " " << progname()
<< " " << _("[ options ] [ action ] file ...\n\n")
<< _("Manipulate the Exif metadata of images.\n");
}
void Params::help(std::ostream& os) const
{
usage(os);
os << _("\nActions:\n")
<< _(" ad | adjust Adjust Exif timestamps by the given time. This\n"
" action requires the option -a time.\n")
<< _(" pr | print Print image metadata.\n")
<< _(" rm | delete Delete image metadata from the files.\n")
<< _(" in | insert Insert metadata from corresponding *.exv files.\n"
" Use option -S to change the suffix of the input files.\n")
<< _(" ex | extract Extract metadata to *.exv and thumbnail image files.\n")
<< _(" mv | rename Rename files and/or set file timestamps according to the\n"
" Exif create timestamp. The filename format can be set with\n"
" -r format, timestamp options are controlled with -t and -T.\n")
<< _(" mo | modify Apply commands to modify (add, set, delete) the Exif and\n"
" Iptc metadata of image files or set the Jpeg comment.\n"
" Requires option -c, -m or -M.\n")
<< _(" fi | fixiso Copy ISO setting from the Nikon Makernote to the regular\n"
" Exif tag.\n")
<< _("\nOptions:\n")
<< _(" -h Display this help and exit.\n")
<< _(" -V Show the program version and exit.\n")
<< _(" -v Be verbose during the program run.\n")
<< _(" -b Show large binary values.\n")
<< _(" -u Don't show unknown tags.\n")
<< _(" -k Preserve file timestamps (keep).\n")
<< _(" -t Also set the file timestamp in 'rename' action (overrides -k).\n")
<< _(" -T Only set the file timestamp in 'rename' action, do not rename\n"
" the file (overrides -k).\n")
<< _(" -f Do not prompt before overwriting existing files (force).\n")
<< _(" -F Do not prompt before renaming files (Force).\n")
<< _(" -a time Time adjustment in the format [-]HH[:MM[:SS]]. This option\n"
" is only used with the 'adjust' action.\n")
<< _(" -p mode Print mode for the 'print' action. Possible modes are:\n")
<< _(" s : print a summary of the Exif metadata (the default)\n")
<< _(" t : interpreted (translated) Exif data (shortcut for -Pkyct)\n")
<< _(" v : plain Exif data values (shortcut for -Pxgnycv)\n")
<< _(" h : hexdump of the Exif data (shortcut for -Pxgnycsh)\n")
<< _(" i : Iptc data values\n")
<< _(" c : Jpeg comment\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")
<< _(" k : key\n")
<< _(" l : tag label\n")
<< _(" n : tag name\n")
<< _(" y : type\n")
<< _(" c : number of components (count)\n")
<< _(" s : size in bytes\n")
<< _(" v : plain data value\n")
<< _(" t : interpreted (translated) data\n")
<< _(" h : hexdump of the data\n")
<< _(" -d tgt Delete target(s) for the 'delete' action. Possible targets are:\n")
<< _(" a : all supported metadata (the default)\n")
<< _(" e : Exif section\n")
<< _(" t : Exif thumbnail only\n")
<< _(" i : Iptc data\n")
<< _(" c : Jpeg comment\n")
<< _(" -i tgt Insert target(s) for the 'insert' action. Possible targets are\n"
" the same as those for the -d option. Only Jpeg thumbnails can\n"
" be inserted, they need to be named <file>-thumb.jpg\n")
<< _(" -e tgt Extract target(s) for the 'extract' action. Possible targets\n"
" 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). The following keywords are supported:\n")
<< _(" :basename: - original filename without extension\n")
<< _(" :dirname: - name of the directory holding the original file\n")
<< _(" :parentname: - name of parent directory\n")
<< _(" Default filename format is ")
<< format_ << ".\n"
<< _(" -c txt Jpeg comment string to set in the image.\n")
<< _(" -m file Command file for the modify action. The format for commands is\n"
" set|add|del <key> [[<type>] <value>].\n")
<< _(" -M cmd Command line for the modify action. The format for the\n"
" commands is the same as that of the lines of a command file.\n")
<< _(" -l dir Location (directory) for files to be inserted from or extracted to.\n")
<< _(" -S .suf Use suffix .suf for source files for insert command.\n\n");
} // Params::help
int Params::option(int opt, const std::string& optarg, int optopt)
{
int rc = 0;
switch (opt) {
case 'h': help_ = true; break;
case 'V': version_ = true; break;
case 'v': verbose_ = true; break;
case 'k': preserve_ = true; break;
case 'b': binary_ = false; break;
case 'u': unknown_ = true; break;
case 'f': force_ = true; fileExistsPolicy_ = overwritePolicy; break;
case 'F': force_ = true; fileExistsPolicy_ = renamePolicy; break;
case 'r': rc = evalRename(opt, optarg); break;
case 't': rc = evalRename(opt, optarg); break;
case 'T': rc = evalRename(opt, optarg); break;
case 'a': rc = evalAdjust(optarg); break;
case 'p': rc = evalPrint(optarg); break;
case 'P': rc = evalPrintCols(optarg); break;
case 'd': rc = evalDelete(optarg); break;
case 'e': rc = evalExtract(optarg); break;
case 'i': rc = evalInsert(optarg); break;
case 'c': rc = evalModify(opt, optarg); break;
case 'm': rc = evalModify(opt, optarg); break;
case 'M': rc = evalModify(opt, optarg); break;
case 'l': directory_ = optarg; break;
case 'S': suffix_ = optarg; break;
case ':':
std::cerr << progname() << ": " << _("Option") << " -" << static_cast<char>(optopt)
<< " " << _("requires an argument\n");
rc = 1;
break;
case '?':
std::cerr << progname() << ": " << _("Unrecognized option") << " -"
<< static_cast<char>(optopt) << "\n";
rc = 1;
break;
default:
std::cerr << progname()
<< ": " << _("getopt returned unexpected character code") << " "
<< std::hex << opt << "\n";
rc = 1;
break;
}
return rc;
} // Params::option
int Params::evalRename(int opt, const std::string& optarg)
{
int rc = 0;
switch (action_) {
case Action::none:
action_ = Action::rename;
switch (opt) {
case 'r':
format_ = optarg;
formatSet_ = true;
break;
case 't': timestamp_ = true; break;
case 'T': timestampOnly_ = true; break;
}
break;
case Action::rename:
if (opt == 'r' && (formatSet_ || timestampOnly_)) {
std::cerr << progname()
<< ": " << _("Ignoring surplus option") << " -r \"" << optarg << "\"\n";
}
else {
format_ = optarg;
formatSet_ = true;
}
break;
default:
std::cerr << progname()
<< ": " << _("Option") << " -" << (char)opt
<< " " << _("is not compatible with a previous option\n");
rc = 1;
break;
}
return rc;
} // Params::evalRename
int Params::evalAdjust(const std::string& optarg)
{
int rc = 0;
switch (action_) {
case Action::none:
action_ = Action::adjust;
adjust_ = parseTime(optarg, adjustment_);
if (!adjust_) {
std::cerr << progname() << ": " << _("Error parsing -a option argument") << " `"
<< optarg << "'\n";
rc = 1;
}
break;
case Action::adjust:
std::cerr << progname()
<< ": " << _("Ignoring surplus option -a") << " " << optarg << "\n";
break;
default:
std::cerr << progname()
<< ": " << _("Option -a is not compatible with a previous option\n");
rc = 1;
break;
}
return rc;
} // Params::evalAdjust
int Params::evalPrint(const std::string& optarg)
{
int rc = 0;
switch (action_) {
case Action::none:
switch (optarg[0]) {
case 's': printMode_ = pmSummary; break;
case 't': rc = evalPrintCols("kyct"); break;
case 'v': rc = evalPrintCols("xgnycv"); break;
case 'h': rc = evalPrintCols("xgnycsh"); break;
case 'i': printMode_ = pmIptc; break;
case 'c': printMode_ = pmComment; break;
default:
std::cerr << progname() << ": " << _("Unrecognized print mode") << " `"
<< optarg << "'\n";
rc = 1;
break;
}
break;
case Action::print:
std::cerr << progname() << ": "
<< _("Ignoring surplus option -p") << optarg << "\n";
break;
default:
std::cerr << progname() << ": "
<< _("Option -p is not compatible with a previous option\n");
rc = 1;
break;
}
return rc;
} // Params::evalPrint
int Params::evalPrintCols(const std::string& optarg)
{
int rc = 0;
switch (action_) {
case Action::none:
action_ = Action::print;
printMode_ = pmList;
for (std::size_t i = 0; i < optarg.length(); ++i) {
switch (optarg[i]) {
case 'x': printItems_ |= prTag; break;
case 'g': printItems_ |= prGroup; break;
case 'k': printItems_ |= prKey; break;
case 'l': printItems_ |= prLabel; break;
case 'n': printItems_ |= prName; break;
case 'y': printItems_ |= prType; break;
case 'c': printItems_ |= prCount; break;
case 's': printItems_ |= prSize; break;
case 'v': printItems_ |= prValue; break;
case 't': printItems_ |= prTrans; break;
case 'h': printItems_ |= prHex; break;
default:
std::cerr << progname() << ": " << _("Unrecognized print item") << " `"
<< optarg[i] << "'\n";
rc = 1;
break;
}
}
break;
case Action::print:
std::cerr << progname() << ": "
<< _("Ignoring surplus option -P") << optarg << "\n";
break;
default:
std::cerr << progname() << ": "
<< _("Option -P is not compatible with a previous option\n");
rc = 1;
break;
}
return rc;
} // Params::evalPrintCols
int Params::evalDelete(const std::string& optarg)
{
int rc = 0;
switch (action_) {
case Action::none:
action_ = Action::erase;
target_ = 0;
// fallthrough
case Action::erase:
rc = parseCommonTargets(optarg, "erase");
if (rc > 0) {
target_ |= rc;
rc = 0;
}
else {
rc = 1;
}
break;
default:
std::cerr << progname() << ": "
<< _("Option -d is not compatible with a previous option\n");
rc = 1;
break;
}
return rc;
} // Params::evalDelete
int Params::evalExtract(const std::string& optarg)
{
int rc = 0;
switch (action_) {
case Action::none:
case Action::modify:
action_ = Action::extract;
target_ = 0;
// fallthrough
case Action::extract:
rc = parseCommonTargets(optarg, "extract");
if (rc > 0) {
target_ |= rc;
rc = 0;
}
else {
rc = 1;
}
break;
default:
std::cerr << progname() << ": "
<< _("Option -e is not compatible with a previous option\n");
rc = 1;
break;
}
return rc;
} // Params::evalExtract
int Params::evalInsert(const std::string& optarg)
{
int rc = 0;
switch (action_) {
case Action::none:
case Action::modify:
action_ = Action::insert;
target_ = 0;
// fallthrough
case Action::insert:
rc = parseCommonTargets(optarg, "insert");
if (rc > 0) {
target_ |= rc;
rc = 0;
}
else {
rc = 1;
}
break;
default:
std::cerr << progname() << ": "
<< _("Option -i is not compatible with a previous option\n");
rc = 1;
break;
}
return rc;
} // Params::evalInsert
int Params::evalModify(int opt, const std::string& optarg)
{
int rc = 0;
switch (action_) {
case Action::none:
action_ = Action::modify;
// fallthrough
case Action::modify:
case Action::extract:
case Action::insert:
if (opt == 'c') jpegComment_ = optarg;
if (opt == 'm') cmdFiles_.push_back(optarg); // parse the files later
if (opt == 'M') cmdLines_.push_back(optarg); // parse the commands later
break;
default:
std::cerr << progname() << ": "
<< _("Option") << " -" << (char)opt << " "
<< _("is not compatible with a previous option\n");
rc = 1;
break;
}
return rc;
} // Params::evalModify
int Params::nonoption(const std::string& argv)
{
int rc = 0;
bool action = false;
if (first_) {
// The first non-option argument must be the action
first_ = false;
if (argv == "ad" || argv == "adjust") {
if (action_ != Action::none && action_ != Action::adjust) {
std::cerr << progname() << ": "
<< _("Action adjust is not compatible with the given options\n");
rc = 1;
}
action = true;
action_ = Action::adjust;
}
if (argv == "pr" || argv == "print") {
if (action_ != Action::none && action_ != Action::print) {
std::cerr << progname() << ": "
<< _("Action print is not compatible with the given options\n");
rc = 1;
}
action = true;
action_ = Action::print;
}
if (argv == "rm" || argv == "delete") {
if (action_ != Action::none && action_ != Action::erase) {
std::cerr << progname() << ": "
<< _("Action delete is not compatible with the given options\n");
rc = 1;
}
action = true;
action_ = Action::erase;
}
if (argv == "ex" || argv == "extract") {
if ( action_ != Action::none
&& action_ != Action::extract
&& action_ != Action::modify) {
std::cerr << progname() << ": "
<< _("Action extract is not compatible with the given options\n");
rc = 1;
}
action = true;
action_ = Action::extract;
}
if (argv == "in" || argv == "insert") {
if ( action_ != Action::none
&& action_ != Action::insert
&& action_ != Action::modify) {
std::cerr << progname() << ": "
<< _("Action insert is not compatible with the given options\n");
rc = 1;
}
action = true;
action_ = Action::insert;
}
if (argv == "mv" || argv == "rename") {
if (action_ != Action::none && action_ != Action::rename) {
std::cerr << progname() << ": "
<< _("Action rename is not compatible with the given options\n");
rc = 1;
}
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 (argv == "fi" || argv == "fixiso") {
if (action_ != Action::none && action_ != Action::fixiso) {
std::cerr << progname() << ": "
<< _("Action fixiso is not compatible with the given options\n");
rc = 1;
}
action = true;
action_ = Action::fixiso;
}
if (action_ == Action::none) {
// if everything else fails, assume print as the default action
action_ = Action::print;
}
}
if (!action) {
files_.push_back(argv);
}
return rc;
} // Params::nonoption
int Params::getopt(int argc, char* const argv[])
{
int rc = Util::Getopt::getopt(argc, argv, optstring_);
// Further consistency checks
if (help_ || version_) return 0;
if (action_ == Action::none) {
// This shouldn't happen since print is taken as default action
std::cerr << progname() << ": " << _("An action must be specified\n");
rc = 1;
}
if (action_ == Action::adjust && !adjust_) {
std::cerr << progname() << ": "
<< _("Adjust action requires option -a time\n");
rc = 1;
}
if ( action_ == Action::modify
&& cmdFiles_.empty() && cmdLines_.empty() && jpegComment_.empty()) {
std::cerr << progname() << ": "
<< _("Modify action requires at least one -c, -m or -M option\n");
rc = 1;
}
if (0 == files_.size()) {
std::cerr << progname() << ": " << _("At least one file is required\n");
rc = 1;
}
if (rc == 0 && !cmdFiles_.empty()) {
// Parse command files
if (!parseCmdFiles(modifyCmds_, cmdFiles_)) {
std::cerr << progname() << ": " << _("Error parsing -m option arguments\n");
rc = 1;
}
}
if (rc == 0 && !cmdLines_.empty()) {
// Parse command lines
if (!parseCmdLines(modifyCmds_, cmdLines_)) {
std::cerr << progname() << ": " << _("Error parsing -M option arguments\n");
rc = 1;
}
}
if ( !directory_.empty()
&& !(action_ == Action::insert || action_ == Action::extract)) {
std::cerr << progname() << ": "
<< _("-l option can only be used with extract or insert actions\n");
rc = 1;
}
if (!suffix_.empty() && !(action_ == Action::insert)) {
std::cerr << progname() << ": "
<< _("-S option can only be used with insert action\n");
rc = 1;
}
if (timestamp_ && !(action_ == Action::rename)) {
std::cerr << progname() << ": "
<< _("-t option can only be used with rename action\n");
rc = 1;
}
if (timestampOnly_ && !(action_ == Action::rename)) {
std::cerr << progname() << ": "
<< _("-T option can only be used with rename action\n");
rc = 1;
}
return rc;
} // Params::getopt
// *****************************************************************************
// local implementations
namespace {
bool parseTime(const std::string& ts, long& time)
{
std::string hstr, mstr, sstr;
char *cts = new char[ts.length() + 1];
strcpy(cts, ts.c_str());
char *tmp = ::strtok(cts, ":");
if (tmp) hstr = tmp;
tmp = ::strtok(0, ":");
if (tmp) mstr = tmp;
tmp = ::strtok(0, ":");
if (tmp) sstr = tmp;
delete[] cts;
int sign = 1;
long hh(0), mm(0), ss(0);
// [-]HH part
if (!Util::strtol(hstr.c_str(), hh)) return false;
if (hh < 0) {
sign = -1;
hh *= -1;
}
// check for the -0 special case
if (hh == 0 && hstr.find('-') != std::string::npos) sign = -1;
// MM part, if there is one
if (mstr != "") {
if (!Util::strtol(mstr.c_str(), mm)) return false;
if (mm > 59) return false;
if (mm < 0) return false;
}
// SS part, if there is one
if (sstr != "") {
if (!Util::strtol(sstr.c_str(), ss)) return false;
if (ss > 59) return false;
if (ss < 0) return false;
}
time = sign * (hh * 3600 + mm * 60 + ss);
return true;
} // parseTime
int parseCommonTargets(const std::string& optarg,
const std::string& action)
{
int rc = 0;
int target = 0;
for (size_t i = 0; rc == 0 && i < optarg.size(); ++i) {
switch (optarg[i]) {
case 'e': target |= Params::ctExif; break;
case 'i': target |= Params::ctIptc; break;
case 'c': target |= Params::ctComment; break;
case 't': target |= Params::ctThumb; break;
case 'a': target |= Params::ctExif
| Params::ctIptc
| Params::ctComment; break;
default:
std::cerr << Params::instance().progname() << ": " << _("Unrecognized ")
<< action << " " << _("target") << " `" << optarg[i] << "'\n";
rc = -1;
break;
}
}
return rc ? rc : target;
} // parseCommonTargets
bool parseCmdFiles(ModifyCmds& modifyCmds,
const Params::CmdFiles& cmdFiles)
{
Params::CmdFiles::const_iterator end = cmdFiles.end();
Params::CmdFiles::const_iterator filename = cmdFiles.begin();
for ( ; filename != end; ++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);
}
}
}
catch (const Exiv2::AnyError& error) {
std::cerr << *filename << ", " << _("line") << " " << error << "\n";
return false;
}
}
return true;
} // parseCmdFile
bool parseCmdLines(ModifyCmds& modifyCmds,
const Params::CmdLines& cmdLines)
{
try {
int num = 0;
Params::CmdLines::const_iterator end = cmdLines.end();
Params::CmdLines::const_iterator line = cmdLines.begin();
for ( ; line != end; ++line) {
ModifyCmd modifyCmd;
if (parseLine(modifyCmd, *line, ++num)) {
modifyCmds.push_back(modifyCmd);
}
}
return true;
}
catch (const Exiv2::AnyError& error) {
std::cerr << _("-M option") << " " << error << "\n";
return false;
}
} // parseCmdLines
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(1, Exiv2::toString(num)
+ ": " + _("Invalid command line"));
}
std::string cmd(line.substr(cmdStart, cmdEnd-cmdStart));
CmdId cmdId = commandId(cmd);
if (cmdId == invalidCmdId) {
throw Exiv2::Error(1, 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::AnyError&) {}
if (metadataId == invalidMetadataId) {
try {
Exiv2::ExifKey exifKey(key);
metadataId = exif;
defaultType = Exiv2::ExifTags::tagType(exifKey.tag(),
exifKey.ifdId());
}
catch (const Exiv2::AnyError&) {}
}
if (metadataId == invalidMetadataId) {
throw Exiv2::Error(1, Exiv2::toString(num)
+ ": " + _("Invalid key") + " `" + key + "'");
}
std::string value;
Exiv2::TypeId type = defaultType;
bool explicitType = false;
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
|| valStart == std::string::npos) {
throw Exiv2::Error(1, Exiv2::toString(num)
+ ": " + _("Invalid command line") + " " );
}
if (typeEnd != std::string::npos) {
std::string typeStr(line.substr(typeStart, typeEnd-typeStart));
Exiv2::TypeId tmpType = Exiv2::TypeInfo::typeId(typeStr);
if (tmpType != Exiv2::invalidTypeId) {
valStart = line.find_first_not_of(delim, typeEnd+1);
if (valStart == std::string::npos) {
throw Exiv2::Error(1, Exiv2::toString(num)
+ ": " + _("Invalid command line") + " " );
}
type = tmpType;
explicitType = true;
}
}
value = line.substr(valStart, valEnd+1-valStart);
std::string::size_type last = value.length()-1;
if ( (value[0] == '"' && value[last] == '"')
|| (value[0] == '\'' && value[last] == '\'')) {
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_;
}
}