Added Exiv2, the application, with print, rename and adjust actions
parent
9cf3e66801
commit
3408dec805
@ -0,0 +1,423 @@
|
|||||||
|
// ***************************************************************** -*- C++ -*-
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2004 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
File: actions.cpp
|
||||||
|
Version: $Name: $ $Revision: 1.1 $
|
||||||
|
Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
|
||||||
|
History: 08-Dec-03, ahu: created
|
||||||
|
*/
|
||||||
|
// *****************************************************************************
|
||||||
|
#include "rcsid.hpp"
|
||||||
|
EXIV2_RCSID("@(#) $Name: $ $Revision: 1.1 $ $RCSfile: actions.cpp,v $")
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// included header files
|
||||||
|
|
||||||
|
#include "actions.hpp"
|
||||||
|
#include "exiv2.hpp"
|
||||||
|
#include "utils.hpp"
|
||||||
|
#include "exif.hpp"
|
||||||
|
|
||||||
|
// + standard includes
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// local declarations
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Convert a string "YYYY:MM:DD HH:MI:SS" to a struct tm type,
|
||||||
|
// returns 0 if successful
|
||||||
|
int str2Tm(const std::string& timeStr, struct tm* tm);
|
||||||
|
|
||||||
|
// Convert a string "YYYY:MM:DD HH:MI:SS" to a time type, -1 on error
|
||||||
|
time_t str2Time(const std::string& timeStr);
|
||||||
|
|
||||||
|
// Convert a time type to a string "YYYY:MM:DD HH:MI:SS", "" on error
|
||||||
|
std::string time2Str(time_t time);
|
||||||
|
|
||||||
|
// Return an error message for the return code of Exif::ExifData::read
|
||||||
|
std::string exifReadError(int rc, const std::string& path);
|
||||||
|
|
||||||
|
// Return an error message for the return code of Exif::ExifData::write
|
||||||
|
std::string exifWriteError(int rc, const std::string& path);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// class member definitions
|
||||||
|
namespace Action {
|
||||||
|
|
||||||
|
Task::AutoPtr Task::clone() const
|
||||||
|
{
|
||||||
|
return AutoPtr(clone_());
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskFactory* TaskFactory::instance_ = 0;
|
||||||
|
|
||||||
|
TaskFactory& TaskFactory::instance()
|
||||||
|
{
|
||||||
|
if (0 == instance_) {
|
||||||
|
instance_ = new TaskFactory;
|
||||||
|
}
|
||||||
|
return *instance_;
|
||||||
|
} // TaskFactory::instance
|
||||||
|
|
||||||
|
void TaskFactory::registerTask(TaskType type, Task::AutoPtr task)
|
||||||
|
{
|
||||||
|
Registry::iterator i = registry_.find(type);
|
||||||
|
if (i != registry_.end()) {
|
||||||
|
delete i->second;
|
||||||
|
}
|
||||||
|
registry_[type] = task.release();
|
||||||
|
} // TaskFactory::registerTask
|
||||||
|
|
||||||
|
TaskFactory::TaskFactory()
|
||||||
|
{
|
||||||
|
// Register a prototype of each known task
|
||||||
|
registerTask(adjust, Task::AutoPtr(new Adjust));
|
||||||
|
registerTask(print, Task::AutoPtr(new Print));
|
||||||
|
registerTask(rename, Task::AutoPtr(new Rename));
|
||||||
|
} // TaskFactory c'tor
|
||||||
|
|
||||||
|
Task::AutoPtr TaskFactory::create(TaskType type)
|
||||||
|
{
|
||||||
|
Registry::const_iterator i = registry_.find(type);
|
||||||
|
if (i != registry_.end() && i->second != 0) {
|
||||||
|
Task* t = i->second;
|
||||||
|
return t->clone();
|
||||||
|
}
|
||||||
|
return Task::AutoPtr(0);
|
||||||
|
} // TaskFactory::create
|
||||||
|
|
||||||
|
int Print::run(const std::string& path)
|
||||||
|
try {
|
||||||
|
Exif::ExifData exifData;
|
||||||
|
int rc = exifData.read(path);
|
||||||
|
if (rc) {
|
||||||
|
std::cerr << exifReadError(rc, path) << "\n";
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
Exif::ExifData::const_iterator md;
|
||||||
|
for (md = exifData.begin(); md != exifData.end(); ++md) {
|
||||||
|
std::cout << "0x" << std::setw(4) << std::setfill('0') << std::right
|
||||||
|
<< std::hex << md->tag() << " "
|
||||||
|
<< std::setw(9) << std::setfill(' ') << std::left
|
||||||
|
<< md->ifdItem() << " "
|
||||||
|
<< std::setw(27) << std::setfill(' ') << std::left
|
||||||
|
<< md->tagName() << " "
|
||||||
|
<< std::dec << md->value() << "\n";
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch(const Exif::Error& e)
|
||||||
|
{
|
||||||
|
std::cerr << "Exif exception in print action for file "
|
||||||
|
<< path << ":\n" << e << "\n";
|
||||||
|
return 1;
|
||||||
|
} // Print::run
|
||||||
|
|
||||||
|
Print::AutoPtr Print::clone() const
|
||||||
|
{
|
||||||
|
return AutoPtr(dynamic_cast<Print*>(clone_()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task* Print::clone_() const
|
||||||
|
{
|
||||||
|
return new Print(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Rename::run(const std::string& path)
|
||||||
|
try {
|
||||||
|
Exif::ExifData exifData;
|
||||||
|
int rc = exifData.read(path);
|
||||||
|
if (rc) {
|
||||||
|
std::cerr << exifReadError(rc, path) << "\n";
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
std::string key = "Image.DateTime.DateTimeOriginal";
|
||||||
|
Exif::ExifData::iterator md = exifData.findKey(key);
|
||||||
|
if (md == exifData.end()) {
|
||||||
|
std::cerr << "Metadatum with key `" << key << "' "
|
||||||
|
<< "not found in the file " << path << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::string v = md->toString();
|
||||||
|
if (v.length() == 0 || v[0] == ' ') {
|
||||||
|
std::cerr << "Image file creation timestamp not set in the file "
|
||||||
|
<< path << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Assemble the new filename from the timestamp
|
||||||
|
struct tm tm;
|
||||||
|
if (str2Tm(v, &tm) != 0) {
|
||||||
|
std::cerr << "Failed to parse timestamp `" << v
|
||||||
|
<< "' in the file " << path << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const size_t max = 1024;
|
||||||
|
char basename[max];
|
||||||
|
memset(basename, 0x0, max);
|
||||||
|
if (strftime(basename, max, Params::instance().format_.c_str(), &tm) == 0) {
|
||||||
|
std::cerr << "Filename format yields empty filename for the file "
|
||||||
|
<< path << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::string newPath
|
||||||
|
= Util::dirname(path) + "/" + basename + Util::suffix(path);
|
||||||
|
if ( Util::dirname(newPath) == Util::dirname(path)
|
||||||
|
&& Util::basename(newPath) == Util::basename(path)) {
|
||||||
|
if (Params::instance().verbose_) {
|
||||||
|
std::cout << "This file already has the correct name\n";
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (Params::instance().verbose_) {
|
||||||
|
std::cout << "Renaming file to " << newPath << "\n";
|
||||||
|
}
|
||||||
|
if (!Params::instance().force_ && Util::fileExists(newPath)) {
|
||||||
|
std::cout << Params::instance().progname()
|
||||||
|
<< ": Overwrite `" << newPath << "'? ";
|
||||||
|
std::string s;
|
||||||
|
std::cin >> s;
|
||||||
|
if (s[0] != 'y' && s[0] != 'Y') return 0;
|
||||||
|
}
|
||||||
|
if (::rename(path.c_str(), newPath.c_str()) == -1) {
|
||||||
|
std::cerr << Params::instance().progname()
|
||||||
|
<< ": Failed to rename "
|
||||||
|
<< path << " to " << newPath << ": "
|
||||||
|
<< Util::strError() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch(const Exif::Error& e)
|
||||||
|
{
|
||||||
|
std::cerr << "Exif exception in rename action for file " << path
|
||||||
|
<< ":\n" << e << "\n";
|
||||||
|
return 1;
|
||||||
|
} // Rename::run
|
||||||
|
|
||||||
|
Rename::AutoPtr Rename::clone() const
|
||||||
|
{
|
||||||
|
return AutoPtr(dynamic_cast<Rename*>(clone_()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task* Rename::clone_() const
|
||||||
|
{
|
||||||
|
return new Rename(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Adjust::run(const std::string& path)
|
||||||
|
try {
|
||||||
|
adjustment_ = Params::instance().adjustment_;
|
||||||
|
|
||||||
|
Exif::ExifData exifData;
|
||||||
|
int rc = exifData.read(path);
|
||||||
|
if (rc) {
|
||||||
|
std::cerr << exifReadError(rc, path) << "\n";
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
rc = adjustDateTime(exifData, "Image.OtherTags.DateTime", path);
|
||||||
|
rc += adjustDateTime(exifData, "Image.DateTime.DateTimeOriginal", path);
|
||||||
|
rc += adjustDateTime(exifData, "Image.DateTime.DateTimeDigitized", path);
|
||||||
|
if (rc) return 1;
|
||||||
|
rc = exifData.write(path);
|
||||||
|
if (rc) {
|
||||||
|
std::cerr << exifWriteError(rc, path) << "\n";
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
catch(const Exif::Error& e)
|
||||||
|
{
|
||||||
|
std::cerr << "Exif exception in adjust action for file " << path
|
||||||
|
<< ":\n" << e << "\n";
|
||||||
|
return 1;
|
||||||
|
} // Adjust::run
|
||||||
|
|
||||||
|
Adjust::AutoPtr Adjust::clone() const
|
||||||
|
{
|
||||||
|
return AutoPtr(dynamic_cast<Adjust*>(clone_()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task* Adjust::clone_() const
|
||||||
|
{
|
||||||
|
return new Adjust(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Adjust::adjustDateTime(Exif::ExifData& exifData,
|
||||||
|
const std::string& key,
|
||||||
|
const std::string& path) const
|
||||||
|
{
|
||||||
|
Exif::ExifData::iterator md = exifData.findKey(key);
|
||||||
|
if (md == exifData.end()) {
|
||||||
|
// Key not found. That's ok, we do nothing.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::string timeStr = md->toString();
|
||||||
|
if (timeStr == "" || timeStr[0] == ' ') {
|
||||||
|
std::cerr << path << ": Timestamp of metadatum with key `"
|
||||||
|
<< key << "' not set\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
time_t time = str2Time(timeStr);
|
||||||
|
if (time == (time_t)-1) {
|
||||||
|
std::cerr << path << ": Failed to parse or convert timestamp `"
|
||||||
|
<< timeStr << "'\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (Params::instance().verbose_) {
|
||||||
|
std::cout << path << ": Adjusting timestamp by"
|
||||||
|
<< (adjustment_ < 0 ? " " : " +")
|
||||||
|
<< adjustment_ << " seconds to ";
|
||||||
|
}
|
||||||
|
time += adjustment_;
|
||||||
|
timeStr = time2Str(time);
|
||||||
|
if (Params::instance().verbose_) {
|
||||||
|
std::cout << timeStr << "\n";
|
||||||
|
}
|
||||||
|
md->setValue(timeStr);
|
||||||
|
return 0;
|
||||||
|
} // Adjust::adjustDateTime
|
||||||
|
|
||||||
|
} // namespace Action
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// local definitions
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
int str2Tm(const std::string& timeStr, struct tm* tm)
|
||||||
|
{
|
||||||
|
if (timeStr.length() == 0 || timeStr[0] == ' ') return 1;
|
||||||
|
if (timeStr.length() < 19) return 2;
|
||||||
|
if ( timeStr[4] != ':' || timeStr[7] != ':' || timeStr[10] != ' '
|
||||||
|
|| timeStr[13] != ':' || timeStr[16] != ':') return 3;
|
||||||
|
if (0 == tm) return 4;
|
||||||
|
::memset(tm, 0x0, sizeof(struct tm));
|
||||||
|
|
||||||
|
long tmp;
|
||||||
|
if (!Util::strtol(timeStr.substr(0,4).c_str(), tmp)) return 5;
|
||||||
|
tm->tm_year = tmp - 1900;
|
||||||
|
if (!Util::strtol(timeStr.substr(5,2).c_str(), tmp)) return 6;
|
||||||
|
tm->tm_mon = tmp - 1;
|
||||||
|
if (!Util::strtol(timeStr.substr(8,2).c_str(), tmp)) return 7;
|
||||||
|
tm->tm_mday = tmp;
|
||||||
|
if (!Util::strtol(timeStr.substr(11,2).c_str(), tmp)) return 8;
|
||||||
|
tm->tm_hour = tmp;
|
||||||
|
if (!Util::strtol(timeStr.substr(14,2).c_str(), tmp)) return 9;
|
||||||
|
tm->tm_min = tmp;
|
||||||
|
if (!Util::strtol(timeStr.substr(17,2).c_str(), tmp)) return 10;
|
||||||
|
tm->tm_sec = tmp;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} // str2Tm
|
||||||
|
|
||||||
|
time_t str2Time(const std::string& timeStr)
|
||||||
|
{
|
||||||
|
struct tm tm;
|
||||||
|
if (str2Tm(timeStr, &tm) != 0) return (time_t)-1;
|
||||||
|
return ::mktime(&tm);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string time2Str(time_t time)
|
||||||
|
{
|
||||||
|
struct tm tm;
|
||||||
|
::memset(&tm, 0x0, sizeof(struct tm));
|
||||||
|
|
||||||
|
if (0 == ::localtime_r(&time, &tm)) return "";
|
||||||
|
|
||||||
|
std::ostringstream os;
|
||||||
|
os << std::setfill('0')
|
||||||
|
<< tm.tm_year + 1900 << ":"
|
||||||
|
<< std::setw(2) << tm.tm_mon + 1 << ":"
|
||||||
|
<< std::setw(2) << tm.tm_mday << " "
|
||||||
|
<< std::setw(2) << tm.tm_hour << ":"
|
||||||
|
<< std::setw(2) << tm.tm_min << ":"
|
||||||
|
<< std::setw(2) << tm.tm_sec;
|
||||||
|
|
||||||
|
return os.str();
|
||||||
|
} // time2Str
|
||||||
|
|
||||||
|
std::string exifReadError(int rc, const std::string& path)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
switch (rc) {
|
||||||
|
case -1:
|
||||||
|
error = "Couldn't open file `" + path + "'";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
error = "Couldn't read from the input stream";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
error = "This does not look like a JPEG image";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
error = "No Exif data found in the file";
|
||||||
|
break;
|
||||||
|
case -99:
|
||||||
|
error = "Unsupported Exif or GPS data found in IFD 1";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = "Reading Exif data failed, rc = " + Exif::toString(rc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
} // exifReadError
|
||||||
|
|
||||||
|
std::string exifWriteError(int rc, const std::string& path)
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
switch (rc) {
|
||||||
|
case -1:
|
||||||
|
error = "Couldn't open file `" + path + "'";
|
||||||
|
break;
|
||||||
|
case -2:
|
||||||
|
error = "Couldn't open temporary file";
|
||||||
|
break;
|
||||||
|
case -3:
|
||||||
|
error = "Renaming temporary file failed";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
error = "Couldn't read from the input stream";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
error = "This does not look like a JPEG image";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
error = "No JFIF APP0 or Exif APP1 segment found in the file";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
error = "Writing to the output stream failed";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = "Reading Exif data failed, rc = " + Exif::toString(rc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
} // exifWriteError
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,187 @@
|
|||||||
|
// ***************************************************************** -*- C++ -*-
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2004 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
/*!
|
||||||
|
@file actions.hpp
|
||||||
|
@brief Implements base class Task, TaskFactory and the various supported
|
||||||
|
actions (derived from Task).
|
||||||
|
@version $Name: $ $Revision: 1.1 $
|
||||||
|
@author Andreas Huggel (ahu)
|
||||||
|
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
|
||||||
|
@date 11-Dec-03, ahu: created
|
||||||
|
*/
|
||||||
|
#ifndef ACTIONS_HPP_
|
||||||
|
#define ACTIONS_HPP_
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// included header files
|
||||||
|
|
||||||
|
// + standard includes
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// class declarations
|
||||||
|
|
||||||
|
namespace Exif {
|
||||||
|
class ExifData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// namespace extensions
|
||||||
|
/*!
|
||||||
|
@brief Contains all action classes (task subclasses).
|
||||||
|
*/
|
||||||
|
namespace Action {
|
||||||
|
|
||||||
|
//! Enumerates all tasks
|
||||||
|
enum TaskType { none, adjust, print, rename };
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// class definitions
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Base class for all concrete actions.
|
||||||
|
|
||||||
|
Task provides a simple interface that actions must implement and a few
|
||||||
|
commonly used helpers.
|
||||||
|
*/
|
||||||
|
class Task {
|
||||||
|
public:
|
||||||
|
//! Shortcut for an auto pointer.
|
||||||
|
typedef std::auto_ptr<Task> AutoPtr;
|
||||||
|
//! Virtual copy construction.
|
||||||
|
AutoPtr clone() const;
|
||||||
|
/*!
|
||||||
|
@brief Application interface to perform a task.
|
||||||
|
|
||||||
|
@param path Path of the file to process.
|
||||||
|
@return 0 if successful.
|
||||||
|
*/
|
||||||
|
virtual int run(const std::string& path) =0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! Internal virtual copy constructor.
|
||||||
|
virtual Task* clone_() const =0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Task factory.
|
||||||
|
|
||||||
|
Creates an instance of the task of the requested type. The factory is
|
||||||
|
implemented as a singleton, which can be accessed only through the static
|
||||||
|
member function instance().
|
||||||
|
*/
|
||||||
|
class TaskFactory {
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
@brief Get access to the task factory.
|
||||||
|
|
||||||
|
Clients access the task factory exclusively through
|
||||||
|
this method.
|
||||||
|
*/
|
||||||
|
static TaskFactory& instance();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Create a task.
|
||||||
|
|
||||||
|
@param type Identifies the type of task to create.
|
||||||
|
@return An auto pointer that owns a task of the requested type. If
|
||||||
|
the task type is not supported, the pointer is 0.
|
||||||
|
@remark The caller of the function should check the content of the
|
||||||
|
returned auto pointer and take appropriate action (e.g., throw
|
||||||
|
an exception) if it is 0.
|
||||||
|
*/
|
||||||
|
Task::AutoPtr create(TaskType type);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Register a task prototype together with its type.
|
||||||
|
|
||||||
|
The task factory creates new tasks of a given type by cloning its
|
||||||
|
associated prototype. Additional tasks can be registered. If called
|
||||||
|
for a type which already exists in the list, the corresponding
|
||||||
|
prototype is replaced.
|
||||||
|
|
||||||
|
@param type Task type.
|
||||||
|
@param task Pointer to the prototype. Ownership is transfered to the
|
||||||
|
task factory. That's what the auto pointer indicates.
|
||||||
|
*/
|
||||||
|
void registerTask(TaskType type, Task::AutoPtr task);
|
||||||
|
|
||||||
|
private:
|
||||||
|
//! Prevent construction other than through instance().
|
||||||
|
TaskFactory();
|
||||||
|
//! Prevent copy construction: not implemented.
|
||||||
|
TaskFactory(const TaskFactory& rhs);
|
||||||
|
|
||||||
|
//! Pointer to the one and only instance of this class.
|
||||||
|
static TaskFactory* instance_;
|
||||||
|
//! Type used to store Task prototype classes
|
||||||
|
typedef std::map<TaskType, Task*> Registry;
|
||||||
|
//! List of task types and corresponding prototypes.
|
||||||
|
Registry registry_;
|
||||||
|
|
||||||
|
}; // class TaskFactory
|
||||||
|
|
||||||
|
//! %Print the %Exif (or other metadata) of a file to stdout
|
||||||
|
class Print : public Task {
|
||||||
|
public:
|
||||||
|
virtual int run(const std::string& path);
|
||||||
|
typedef std::auto_ptr<Print> AutoPtr;
|
||||||
|
AutoPtr clone() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual Task* clone_() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief %Rename a file to its metadate creation timestamp,
|
||||||
|
in the specified format.
|
||||||
|
*/
|
||||||
|
class Rename : public Task {
|
||||||
|
public:
|
||||||
|
virtual int run(const std::string& path);
|
||||||
|
typedef std::auto_ptr<Rename> AutoPtr;
|
||||||
|
AutoPtr clone() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual Task* clone_() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
//! %Adjust the %Exif (or other metadata) timestamps
|
||||||
|
class Adjust : public Task {
|
||||||
|
public:
|
||||||
|
virtual int run(const std::string& path);
|
||||||
|
typedef std::auto_ptr<Adjust> AutoPtr;
|
||||||
|
AutoPtr clone() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual Task* clone_() const;
|
||||||
|
int adjustDateTime(Exif::ExifData& exifData,
|
||||||
|
const std::string& key,
|
||||||
|
const std::string& path) const;
|
||||||
|
|
||||||
|
long adjustment_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Action
|
||||||
|
|
||||||
|
#endif // #ifndef ACTIONS_HPP_
|
@ -0,0 +1,285 @@
|
|||||||
|
// ***************************************************************** -*- C++ -*-
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2004 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
Abstract: Command line program to display and manipulate image %Exif data
|
||||||
|
|
||||||
|
File: exiv2.cpp
|
||||||
|
Version: $Name: $ $Revision: 1.1 $
|
||||||
|
Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
|
||||||
|
History: 10-Dec-03, ahu: created
|
||||||
|
*/
|
||||||
|
// *****************************************************************************
|
||||||
|
#include "rcsid.hpp"
|
||||||
|
EXIV2_RCSID("@(#) $Name: $ $Revision: 1.1 $ $RCSfile: exiv2.cpp,v $")
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// included header files
|
||||||
|
#include "exiv2.hpp"
|
||||||
|
#include "actions.hpp"
|
||||||
|
#include "utils.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
// *********************************************************************
|
||||||
|
// local declarations
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// Main
|
||||||
|
int main(int argc, char* const argv[])
|
||||||
|
{
|
||||||
|
// 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 = 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 << "\n";
|
||||||
|
}
|
||||||
|
task->run(*i);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} // main
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// class Params
|
||||||
|
Params* Params::instance_ = 0;
|
||||||
|
|
||||||
|
Params& Params::instance()
|
||||||
|
{
|
||||||
|
if (0 == instance_) {
|
||||||
|
instance_ = new Params;
|
||||||
|
}
|
||||||
|
return *instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Params::version(std::ostream& os) const
|
||||||
|
{
|
||||||
|
os << "Exiv2 version 0.3, "
|
||||||
|
<< "Copyright (C) 2004 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";
|
||||||
|
}
|
||||||
|
|
||||||
|
void Params::usage(std::ostream& os) const
|
||||||
|
{
|
||||||
|
os << "Usage: " << progname()
|
||||||
|
<< " [ -hVvf ][ -a time ][ -r format ] action file ...\n\n"
|
||||||
|
<< "Manipulate the Exif metadata of images.\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void Params::help(std::ostream& os) const
|
||||||
|
{
|
||||||
|
usage(os);
|
||||||
|
os << "\nOptions:\n"
|
||||||
|
<< " -h Display this help and exit.\n"
|
||||||
|
<< " -V Show the program version and exit.\n"
|
||||||
|
<< " -v Be more verbose during the program run.\n"
|
||||||
|
<< " -f Do not prompt before overwriting existing files (force).\n"
|
||||||
|
<< " -a time Time adjustment in the format [-]HH[:MM[:SS]]. This option\n"
|
||||||
|
<< " is only used with the `adjust' action.\n"
|
||||||
|
<< " -r fmt Filename format for the `rename' action. The format string\n"
|
||||||
|
<< " follows strftime(3). Default filename format is "
|
||||||
|
<< format_ << ".\n"
|
||||||
|
<< "Actions:\n"
|
||||||
|
<< " adjust Adjust the metadata timestamp by the given time. This action\n"
|
||||||
|
<< " requires the option -a time.\n"
|
||||||
|
<< " print Print the Exif (or other) image metadata.\n"
|
||||||
|
<< " rename Rename files according to the metadata create timestamp. The\n"
|
||||||
|
<< " filename format can be set with the option -r format.\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 'f':
|
||||||
|
force_ = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
adjust_ = parseTime(optarg, adjustment_);
|
||||||
|
if (!adjust_) {
|
||||||
|
std::cerr << progname() << ": Error parsing -a option argument `"
|
||||||
|
<< optarg << "'\n";
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
format_ = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
} // Params::option
|
||||||
|
|
||||||
|
int Params::nonoption(const std::string& argv)
|
||||||
|
{
|
||||||
|
int rc = 0;
|
||||||
|
if (first_) {
|
||||||
|
// The first non-option argument must be the action
|
||||||
|
first_ = false;
|
||||||
|
if (argv == "adjust") action_ = Action::adjust;
|
||||||
|
if (argv == "print") action_ = Action::print;
|
||||||
|
if (argv == "rename") action_ = Action::rename;
|
||||||
|
if (action_ == Action::none) {
|
||||||
|
std::cerr << progname() << ": Unrecognized action `"
|
||||||
|
<< argv << "'\n";
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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) {
|
||||||
|
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 (0 == files_.size()) {
|
||||||
|
std::cerr << progname() << ": At least one file is required\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
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,157 @@
|
|||||||
|
// ***************************************************************** -*- C++ -*-
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2004 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*/
|
||||||
|
/*!
|
||||||
|
@file exiv2.hpp
|
||||||
|
@brief Defines class Params, used for the command line handling of exiv2
|
||||||
|
@version $Name: $ $Revision: 1.1 $
|
||||||
|
@author Andreas Huggel (ahu)
|
||||||
|
<a href="mailto:ahuggel@gmx.net">ahuggel@gmx.net</a>
|
||||||
|
@date 08-Dec-03, ahu: created
|
||||||
|
*/
|
||||||
|
#ifndef EXIV2_HPP_
|
||||||
|
#define EXIV2_HPP_
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// included header files
|
||||||
|
#include "utils.hpp"
|
||||||
|
|
||||||
|
// + standard includes
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// class definitions
|
||||||
|
/*!
|
||||||
|
@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,
|
||||||
|
i.e., there is only one global instance of it, which can be accessed
|
||||||
|
from everywhere.
|
||||||
|
|
||||||
|
<b>Usage example:</b> <br>
|
||||||
|
@code
|
||||||
|
#include "params.h"
|
||||||
|
|
||||||
|
int main(int argc, char* const argv[])
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do something useful here...
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@endcode
|
||||||
|
*/
|
||||||
|
class Params : public Util::Getopt {
|
||||||
|
private:
|
||||||
|
std::string optstring_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
@brief Controls all access to the global Params instance.
|
||||||
|
@return Reference to the global Params instance.
|
||||||
|
*/
|
||||||
|
static Params& instance();
|
||||||
|
|
||||||
|
bool help_; //!< Help option flag.
|
||||||
|
bool version_; //!< Version option flag.
|
||||||
|
bool verbose_; //!< Verbose (talkative) option flag.
|
||||||
|
bool force_; //!< Force overwrites flag.
|
||||||
|
bool adjust_; //!< Adjustment flag.
|
||||||
|
//! %Action (integer rather than TaskType to avoid dependency).
|
||||||
|
int action_;
|
||||||
|
|
||||||
|
long adjustment_; //!< Adjustment in seconds.
|
||||||
|
std::string format_; //!< Filename format (-r option arg).
|
||||||
|
|
||||||
|
//! Container to store filenames.
|
||||||
|
typedef std::vector<std::string> Files;
|
||||||
|
|
||||||
|
Files files_; //!< List of non-option arguments.
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*!
|
||||||
|
@brief Default constructor. Note that optstring_ is initialized here.
|
||||||
|
Private to force instantiation through instance().
|
||||||
|
*/
|
||||||
|
Params() : optstring_(":hVvfa:r:"),
|
||||||
|
help_(false),
|
||||||
|
version_(false),
|
||||||
|
verbose_(false),
|
||||||
|
force_(false),
|
||||||
|
adjust_(false),
|
||||||
|
action_(0),
|
||||||
|
adjustment_(0),
|
||||||
|
format_("%Y%m%d_%H%M%S"),
|
||||||
|
first_(true) {}
|
||||||
|
|
||||||
|
//! Prevent copy-construction: not implemented.
|
||||||
|
Params(const Params& rhs);
|
||||||
|
|
||||||
|
//! Pointer to the global Params object.
|
||||||
|
static Params* instance_;
|
||||||
|
|
||||||
|
bool first_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
@brief Call Getopt::getopt() with optstring, to inititate command line
|
||||||
|
argument parsing, perform consistency checks after all command line
|
||||||
|
arguments are parsed.
|
||||||
|
|
||||||
|
@param argc Argument count as passed to main() on program invocation.
|
||||||
|
@param argv Argument array as passed to main() on program invocation.
|
||||||
|
|
||||||
|
@return 0 if successful, >0 in case of errors.
|
||||||
|
*/
|
||||||
|
int getopt(int argc, char* const argv[]);
|
||||||
|
|
||||||
|
//! Handle options and their arguments.
|
||||||
|
virtual int option(int opt, const std::string& optarg, int optopt);
|
||||||
|
|
||||||
|
//! Handle non-option parameters.
|
||||||
|
virtual int nonoption(const std::string& argv);
|
||||||
|
|
||||||
|
//! Print a minimal usage note to an output stream.
|
||||||
|
void usage(std::ostream& os =std::cout) const;
|
||||||
|
|
||||||
|
//! Print further usage explanations to an output stream.
|
||||||
|
void help(std::ostream& os =std::cout) const;
|
||||||
|
|
||||||
|
//! Print version information to an output stream.
|
||||||
|
void version(std::ostream& os =std::cout) const;
|
||||||
|
}; // class Params
|
||||||
|
|
||||||
|
#endif // #ifndef EXIV2_HPP_
|
Loading…
Reference in New Issue