parent
842ef05ee6
commit
007137939e
@ -1,235 +0,0 @@
|
||||
/*
|
||||
* This code implements the MD5 message-digest algorithm.
|
||||
* The algorithm is due to Ron Rivest. This code was
|
||||
* written by Colin Plumb in 1993, no copyright is claimed.
|
||||
* This code is in the public domain; do with it what you wish.
|
||||
*
|
||||
* Equivalent code is available from RSA Data Security, Inc.
|
||||
* This code has been tested against that, and is equivalent,
|
||||
* except that you don't need to include two pages of legalese
|
||||
* with every copy.
|
||||
*
|
||||
* To compute the message digest of a chunk of bytes, declare an
|
||||
* MD5_CTX structure, pass it to MD5Init, call MD5Update as
|
||||
* needed on buffers full of bytes, and then call MD5Final, which
|
||||
* will fill a supplied 16-byte array with the digest.
|
||||
*
|
||||
* Changed so as no longer to depend on Colin Plumb's `usual.h' header
|
||||
* definitions; now uses stuff from dpkg's config.h.
|
||||
* - Ian Jackson <ian@chiark.greenend.org.uk>.
|
||||
* Still in the public domain.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "MD5.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void
|
||||
byteSwap(UWORD32 *buf, unsigned words)
|
||||
{
|
||||
const uint32_t byteOrderTest = 0x1;
|
||||
if (((char *)&byteOrderTest)[0] == 0) {
|
||||
md5byte *p = (md5byte *)buf;
|
||||
|
||||
do {
|
||||
*buf++ = (UWORD32)((unsigned)p[3] << 8 | p[2]) << 16 |
|
||||
((unsigned)p[1] << 8 | p[0]);
|
||||
p += 4;
|
||||
} while (--words);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
|
||||
* initialization constants.
|
||||
*/
|
||||
void
|
||||
MD5Init(struct MD5_CTX *ctx)
|
||||
{
|
||||
ctx->buf[0] = 0x67452301;
|
||||
ctx->buf[1] = 0xefcdab89;
|
||||
ctx->buf[2] = 0x98badcfe;
|
||||
ctx->buf[3] = 0x10325476;
|
||||
|
||||
ctx->bytes[0] = 0;
|
||||
ctx->bytes[1] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update context to reflect the concatenation of another buffer full
|
||||
* of bytes.
|
||||
*/
|
||||
void
|
||||
MD5Update(struct MD5_CTX *ctx, md5byte const *buf, unsigned len)
|
||||
{
|
||||
UWORD32 t;
|
||||
|
||||
/* Update byte count */
|
||||
|
||||
t = ctx->bytes[0];
|
||||
if ((ctx->bytes[0] = t + len) < t)
|
||||
ctx->bytes[1]++; /* Carry from low to high */
|
||||
|
||||
t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */
|
||||
if (t > len) {
|
||||
memcpy((md5byte *)ctx->in + 64 - t, buf, len);
|
||||
return;
|
||||
}
|
||||
/* First chunk is an odd size */
|
||||
memcpy((md5byte *)ctx->in + 64 - t, buf, t);
|
||||
byteSwap(ctx->in, 16);
|
||||
MD5Transform(ctx->buf, ctx->in);
|
||||
buf += t;
|
||||
len -= t;
|
||||
|
||||
/* Process data in 64-byte chunks */
|
||||
while (len >= 64) {
|
||||
memcpy(ctx->in, buf, 64);
|
||||
byteSwap(ctx->in, 16);
|
||||
MD5Transform(ctx->buf, ctx->in);
|
||||
buf += 64;
|
||||
len -= 64;
|
||||
}
|
||||
|
||||
/* Handle any remaining bytes of data. */
|
||||
memcpy(ctx->in, buf, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Final wrapup - pad to 64-byte boundary with the bit pattern
|
||||
* 1 0* (64-bit count of bits processed, MSB-first)
|
||||
*/
|
||||
void
|
||||
MD5Final(md5byte digest[16], struct MD5_CTX *ctx)
|
||||
{
|
||||
int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */
|
||||
md5byte *p = (md5byte *)ctx->in + count;
|
||||
|
||||
/* Set the first char of padding to 0x80. There is always room. */
|
||||
*p++ = 0x80;
|
||||
|
||||
/* Bytes of padding needed to make 56 bytes (-8..55) */
|
||||
count = 56 - 1 - count;
|
||||
|
||||
if (count < 0) { /* Padding forces an extra block */
|
||||
memset(p, 0, count + 8);
|
||||
byteSwap(ctx->in, 16);
|
||||
MD5Transform(ctx->buf, ctx->in);
|
||||
p = (md5byte *)ctx->in;
|
||||
count = 56;
|
||||
}
|
||||
memset(p, 0, count);
|
||||
byteSwap(ctx->in, 14);
|
||||
|
||||
/* Append length in bits and transform */
|
||||
ctx->in[14] = ctx->bytes[0] << 3;
|
||||
ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29;
|
||||
MD5Transform(ctx->buf, ctx->in);
|
||||
|
||||
byteSwap(ctx->buf, 4);
|
||||
memcpy(digest, ctx->buf, 16);
|
||||
memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */
|
||||
}
|
||||
|
||||
/* The four core functions - F1 is optimized somewhat */
|
||||
|
||||
/* #define F1(x, y, z) (x & y | ~x & z) */
|
||||
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
||||
#define F2(x, y, z) F1(z, x, y)
|
||||
#define F3(x, y, z) (x ^ y ^ z)
|
||||
#define F4(x, y, z) (y ^ (x | ~z))
|
||||
|
||||
/* This is the central step in the MD5 algorithm. */
|
||||
#define MD5STEP(f,w,x,y,z,in,s) \
|
||||
(w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
|
||||
|
||||
/*
|
||||
* The core of the MD5 algorithm, this alters an existing MD5 hash to
|
||||
* reflect the addition of 16 longwords of new data. MD5Update blocks
|
||||
* the data and converts bytes into longwords for this routine.
|
||||
*/
|
||||
void
|
||||
MD5Transform(UWORD32 buf[4], UWORD32 const in[16])
|
||||
{
|
||||
register UWORD32 a, b, c, d;
|
||||
|
||||
a = buf[0];
|
||||
b = buf[1];
|
||||
c = buf[2];
|
||||
d = buf[3];
|
||||
|
||||
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
|
||||
|
||||
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
|
||||
|
||||
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
|
||||
|
||||
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
|
||||
|
||||
buf[0] += a;
|
||||
buf[1] += b;
|
||||
buf[2] += c;
|
||||
buf[3] += d;
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
#ifndef __MD5_h__
|
||||
#define __MD5_h__
|
||||
|
||||
/*
|
||||
* This is the header file for the MD5 message-digest algorithm.
|
||||
* The algorithm is due to Ron Rivest. This code was
|
||||
* written by Colin Plumb in 1993, no copyright is claimed.
|
||||
* This code is in the public domain; do with it what you wish.
|
||||
*
|
||||
* Equivalent code is available from RSA Data Security, Inc.
|
||||
* This code has been tested against that, and is equivalent,
|
||||
* except that you don't need to include two pages of legalese
|
||||
* with every copy.
|
||||
*
|
||||
* To compute the message digest of a chunk of bytes, declare an
|
||||
* MD5_CTX structure, pass it to MD5Init, call MD5Update as
|
||||
* needed on buffers full of bytes, and then call MD5Final, which
|
||||
* will fill a supplied 16-byte array with the digest.
|
||||
*
|
||||
* Changed so as no longer to depend on Colin Plumb's `usual.h'
|
||||
* header definitions; now uses stuff from dpkg's config.h
|
||||
* - Ian Jackson <ian@chiark.greenend.org.uk>.
|
||||
* Still in the public domain.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#ifdef EXV_HAVE_STDINT_H
|
||||
# include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* MSVC doesn't provide C99 types, but it has MS specific variants */
|
||||
#ifdef _MSC_VER
|
||||
typedef unsigned __int32 uint32_t;
|
||||
#endif
|
||||
|
||||
typedef unsigned char md5byte;
|
||||
typedef uint32_t UWORD32;
|
||||
|
||||
struct MD5_CTX {
|
||||
UWORD32 buf[4];
|
||||
UWORD32 bytes[2];
|
||||
UWORD32 in[16];
|
||||
};
|
||||
|
||||
extern void MD5Init(struct MD5_CTX *context);
|
||||
extern void MD5Update(struct MD5_CTX *context, md5byte const *buf, unsigned len);
|
||||
extern void MD5Final(unsigned char digest[16], struct MD5_CTX *context);
|
||||
extern void MD5Transform(UWORD32 buf[4], UWORD32 const in[16]);
|
||||
|
||||
#endif
|
@ -1,144 +0,0 @@
|
||||
# ************************************************************* -*- Makefile -*-
|
||||
#
|
||||
# Copyright (C) 2004-2015 Andreas Huggel <ahuggel@gmx.net>
|
||||
#
|
||||
# This Makefile is part of the Exiv2 distribution.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
# 3. The name of the author may not be used to endorse or promote
|
||||
# products derived from this software without specific prior
|
||||
# written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# File: Makefile
|
||||
# Author(s): Andreas Huggel (ahu) <ahuggel@gmx.net>
|
||||
# History: 31-Jan-09, ahu: created
|
||||
#
|
||||
# Description:
|
||||
# Simple Makefile to build the organize application. Requires installed
|
||||
# exiv2 library and headers. Adapted from samples/Makefile.
|
||||
#
|
||||
# Restrictions:
|
||||
# Requires GNU make.
|
||||
#
|
||||
|
||||
# ******************************************************************************
|
||||
# Default make target
|
||||
all: ozbin
|
||||
|
||||
# Include system configuration
|
||||
top_srcdir = ../..
|
||||
include $(top_srcdir)/config/config.mk
|
||||
include boost.mk
|
||||
|
||||
# ******************************************************************************
|
||||
# Source files
|
||||
|
||||
# Source files for the organize application
|
||||
OZMAIN = organize.cpp
|
||||
OZSRC = helpers.cpp MD5.cpp
|
||||
|
||||
# ******************************************************************************
|
||||
# Initialisations
|
||||
SHELL = /bin/sh
|
||||
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .c .cpp .o .so
|
||||
|
||||
.PRECIOUS: %.cpp
|
||||
|
||||
CPPFLAGS := -I$(BOOST_INC_DIR) `pkg-config exiv2 --cflags`
|
||||
ifdef HAVE_STDINT
|
||||
CPPFLAGS += -DEXV_HAVE_STDINT_H=1
|
||||
endif
|
||||
|
||||
LDFLAGS := $(BOOST_LIBS) `pkg-config exiv2 --libs`
|
||||
|
||||
OZOBJ = $(OZSRC:.cpp=.o) $(OZMAIN:.cpp=.o)
|
||||
OZBIN = $(OZMAIN:.cpp=)
|
||||
OZEXE = $(OZMAIN:.cpp=$(EXEEXT))
|
||||
|
||||
ifdef DEP_TRACKING
|
||||
DEP = $(OZMAIN:%.cpp=$(DEPDIR)/%.d) $(OZSRC:%.cpp=$(DEPDIR)/%.d)
|
||||
endif
|
||||
|
||||
# ******************************************************************************
|
||||
# Rules
|
||||
ozbin: $(OZBIN)
|
||||
|
||||
$(OZOBJ): %.o: %.cpp
|
||||
$(COMPILE.cc) -o $@ $<
|
||||
@$(MAKEDEPEND)
|
||||
@$(POSTDEPEND)
|
||||
|
||||
%.ii: %.cpp
|
||||
set -e; \
|
||||
$(CXXCPP) -E $(CPPFLAGS) $< | sed '/^[ ]*$$/d' > $@
|
||||
|
||||
# ******************************************************************************
|
||||
# Targets
|
||||
.PHONY: all ozbin relink binclean install uninstall mostlyclean clean distclean maintainer-clean
|
||||
|
||||
ifdef DEP_TRACKING
|
||||
# Include targets from dependency files
|
||||
-include $(DEP)
|
||||
endif
|
||||
|
||||
$(OZBIN): $(OZOBJ)
|
||||
$(LIBTOOL) --mode=link $(LINK.cc) -o $@ $(OZOBJ)
|
||||
|
||||
relink: binclean organize
|
||||
|
||||
install:
|
||||
$(INSTALL_DIRS) $(DESTDIR)$(bindir)
|
||||
@$(LIBTOOL) --mode=install $(INSTALL_PROGRAM) $(OZEXE) $(DESTDIR)$(bindir)/$(OZEXE)
|
||||
|
||||
uninstall:
|
||||
@$(LIBTOOL) --mode=uninstall $(RM) $(DESTDIR)$(bindir)/$(OZEXE)
|
||||
-rmdir $(DESTDIR)$(bindir)
|
||||
|
||||
# Remove binaries, e.g., to relink them
|
||||
binclean:
|
||||
$(RM) $(OZEXE)
|
||||
|
||||
mostlyclean:
|
||||
$(RM) core
|
||||
$(RM) $(OZMAIN:.cpp=.ii) $(OZSRC:.cpp=.ii)
|
||||
$(RM) $(OZMAIN:%.cpp=.libs/%.d) $(OZSRC:%.cpp=.libs/%.d)
|
||||
-rmdir .libs
|
||||
$(RM) $(OZOBJ)
|
||||
|
||||
clean: binclean mostlyclean
|
||||
|
||||
# Run `make distclean' from the top source directory to also remove
|
||||
# files created by configuring the program.
|
||||
distclean: clean
|
||||
ifdef DEP_TRACKING
|
||||
$(RM) $(DEP)
|
||||
-rmdir $(DEPDIR)
|
||||
endif
|
||||
$(RM) *~ *.bak *#
|
||||
|
||||
# This command is intended for maintainers to use; it deletes files
|
||||
# that may need special tools to rebuild.
|
||||
maintainer-clean: uninstall distclean
|
@ -1,3 +0,0 @@
|
||||
organize uses the Boost library (http://www.boost.org).
|
||||
Configuration settings for Boost are in the file boost.mk
|
||||
in this directory and should be changed as required.
|
@ -1,3 +0,0 @@
|
||||
# Boost configuration for organize - change paths and library names as needed
|
||||
BOOST_INC_DIR = /usr/local/include/boost-1_37
|
||||
BOOST_LIBS = /usr/local/lib/libboost_system-gcc43-mt-1_37.a /usr/local/lib/libboost_filesystem-gcc43-mt-1_37.a /usr/local/lib/libboost_regex-gcc43-mt-1_37.a /usr/local/lib/libboost_program_options-gcc43-mt-1_37.a
|
@ -1,635 +0,0 @@
|
||||
// ***************************************************************** -*- C++ -*-
|
||||
/*
|
||||
* Copyright (C) 2009 Brad Schick <schickb@gmail.com>
|
||||
*
|
||||
* This file is part of the organize tool.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
// *****************************************************************************
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <exiv2/image.hpp>
|
||||
#include <exiv2/easyaccess.hpp>
|
||||
#include <exiv2/exif.hpp>
|
||||
#include <exiv2/iptc.hpp>
|
||||
#include <exiv2/tags.hpp>
|
||||
//#include <exiv2/xmp.hpp>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <ctime>
|
||||
#include "helpers.hpp"
|
||||
|
||||
#define BOOST_FILESYSTEM_NO_DEPRECATED
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
typedef Exiv2::ExifData::const_iterator (*EasyAccessFct)(const Exiv2::ExifData& ed);
|
||||
|
||||
|
||||
std::string scrub(const std::string &dirty, bool strip_space = false)
|
||||
{
|
||||
std::string scrub = boost::trim_copy(dirty);
|
||||
if(strip_space) {
|
||||
boost::regex space("\\s");
|
||||
scrub = boost::regex_replace(scrub, space, "");
|
||||
}
|
||||
boost::regex dash("[:/\\\\|<>]");
|
||||
boost::regex under("[\"'\\[\\]\\{\\}#=%\\$\\?,\\+\\*]");
|
||||
scrub = boost::regex_replace(scrub, dash, "-");
|
||||
|
||||
return boost::regex_replace(scrub, under, "_");
|
||||
}
|
||||
|
||||
bool exif_data(const Exiv2::Image *image, const char *key, Exiv2::ExifData::const_iterator &md)
|
||||
{
|
||||
assert(image && key);
|
||||
bool ok = false;
|
||||
try {
|
||||
const Exiv2::ExifData &exifData = image->exifData();
|
||||
Exiv2::ExifKey exifKey(key);
|
||||
md = exifData.findKey(exifKey);
|
||||
if(md != exifData.end() && md->typeId() != Exiv2::undefined)
|
||||
ok = true;
|
||||
}
|
||||
catch(const Exiv2::AnyError&) {
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool exif_data_easy(const Exiv2::Image *image, EasyAccessFct easy, Exiv2::ExifData::const_iterator &md)
|
||||
{
|
||||
assert(image && easy);
|
||||
bool ok = false;
|
||||
try {
|
||||
const Exiv2::ExifData &exifData = image->exifData();
|
||||
md = easy(exifData);
|
||||
if(md != exifData.end() && md->typeId() != Exiv2::undefined)
|
||||
ok = true;
|
||||
}
|
||||
catch(const Exiv2::AnyError&) {
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
bool iptc_data(const Exiv2::Image *image, const char *key, Exiv2::IptcData::const_iterator &md)
|
||||
{
|
||||
bool ok = false;
|
||||
assert(image && key);
|
||||
try {
|
||||
const Exiv2::IptcData &iptcData = image->iptcData();
|
||||
Exiv2::IptcKey iptcKey(key);
|
||||
md = iptcData.findKey(iptcKey);
|
||||
if(md != iptcData.end() && md->typeId() != Exiv2::undefined)
|
||||
ok = true;
|
||||
}
|
||||
catch(const Exiv2::AnyError&) {
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
std::string exif_date(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Photo.DateTimeDigitized", md);
|
||||
if(!done)
|
||||
done = exif_data(image, "Exif.Photo.DateTimeOriginal", md);
|
||||
if(!done)
|
||||
return "";
|
||||
|
||||
std::string date = scrub(md->print().substr(0,10));
|
||||
// Some files have zeros for dates, just fail in that case
|
||||
if(boost::lexical_cast<int>(date.substr(0,4))==0)
|
||||
return "";
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
std::string exif_year(const Exiv2::Image *image, const fs::path &path)
|
||||
{
|
||||
std::string date = exif_date(image, path);
|
||||
if(date.length())
|
||||
return date.substr(0,4);
|
||||
else
|
||||
return date;
|
||||
}
|
||||
|
||||
std::string exif_month(const Exiv2::Image *image, const fs::path &path)
|
||||
{
|
||||
std::string date = exif_date(image, path);
|
||||
if(date.length())
|
||||
return date.substr(5,2);
|
||||
else
|
||||
return date;
|
||||
}
|
||||
|
||||
std::string exif_day(const Exiv2::Image *image, const fs::path &path)
|
||||
{
|
||||
std::string date = exif_date(image, path);
|
||||
if(date.length())
|
||||
return date.substr(8,2);
|
||||
else
|
||||
return date;
|
||||
}
|
||||
|
||||
bool iptc_get_date(const Exiv2::Image *image, Exiv2::DateValue::Date &date)
|
||||
{
|
||||
Exiv2::IptcData::const_iterator md;
|
||||
bool done = iptc_data(image, "Iptc.Application2.DigitizationDate", md);
|
||||
if(!done)
|
||||
done = iptc_data(image, "Iptc.Application2.DateCreated", md);
|
||||
if(!done)
|
||||
return false;
|
||||
date = ((Exiv2::DateValue*)md->getValue().get())->getDate();
|
||||
return date.year > 0;
|
||||
}
|
||||
|
||||
std::string iptc_date(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::DateValue::Date date;
|
||||
if(iptc_get_date(image, date))
|
||||
return str(boost::format("%4d-%02d-%02d") % date.year % date.month % date.day);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string iptc_year(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::DateValue::Date date;
|
||||
if(iptc_get_date(image, date))
|
||||
return str(boost::format("%4d") % date.year);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string iptc_month(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::DateValue::Date date;
|
||||
if(iptc_get_date(image, date))
|
||||
return str(boost::format("%02d") % date.month);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string iptc_day(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::DateValue::Date date;
|
||||
if(iptc_get_date(image, date))
|
||||
return str(boost::format("%02d") % date.day);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
bool file_get_tm(const fs::path &path, std::tm &tm)
|
||||
{
|
||||
std::time_t timer = fs::last_write_time(path);
|
||||
if(time > 0) {
|
||||
tm = *localtime(&timer);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string file_date(const Exiv2::Image *, const fs::path &path)
|
||||
{
|
||||
std::tm tm;
|
||||
if(file_get_tm(path, tm))
|
||||
return str(boost::format("%4d-%02d-%02d") % (tm.tm_year + 1900) % (tm.tm_mon + 1) % tm.tm_mday);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string file_year(const Exiv2::Image *, const fs::path &path)
|
||||
{
|
||||
std::tm tm;
|
||||
if(file_get_tm(path, tm))
|
||||
return str(boost::format("%4d") % (tm.tm_year + 1900));
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string file_month(const Exiv2::Image *, const fs::path &path)
|
||||
{
|
||||
std::tm tm;
|
||||
if(file_get_tm(path, tm))
|
||||
return str(boost::format("%02d") % (tm.tm_mon + 1));
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string file_day(const Exiv2::Image *, const fs::path &path)
|
||||
{
|
||||
std::tm tm;
|
||||
if(file_get_tm(path, tm))
|
||||
return str(boost::format("%02d") % tm.tm_mday);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
std::string xmp_date(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string xmp_year(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string xmp_month(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string xmp_day(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}*/
|
||||
|
||||
std::string exif_time(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Photo.DateTimeDigitized", md);
|
||||
if(!done)
|
||||
done = exif_data(image, "Exif.Photo.DateTimeOriginal", md);
|
||||
if(!done)
|
||||
return "";
|
||||
|
||||
std::string datetime = md->print();
|
||||
// Some files have zeros for dates, just fail in that case
|
||||
if(boost::lexical_cast<int>(datetime.substr(0,4)) == 0)
|
||||
return "";
|
||||
|
||||
return scrub(datetime.substr(11));
|
||||
}
|
||||
|
||||
std::string exif_hour(const Exiv2::Image *image, const fs::path &path)
|
||||
{
|
||||
std::string time = exif_time(image, path);
|
||||
if(time.length())
|
||||
return time.substr(0,2);
|
||||
else
|
||||
return time;
|
||||
}
|
||||
|
||||
std::string exif_minute(const Exiv2::Image *image, const fs::path &path)
|
||||
{
|
||||
std::string time = exif_time(image, path);
|
||||
if(time.length())
|
||||
return time.substr(3,2);
|
||||
else
|
||||
return time;
|
||||
}
|
||||
|
||||
std::string exif_second(const Exiv2::Image *image, const fs::path &path)
|
||||
{
|
||||
std::string time = exif_time(image, path);
|
||||
if(time.length())
|
||||
return time.substr(6,2);
|
||||
else
|
||||
return time;
|
||||
}
|
||||
|
||||
bool iptc_get_time(const Exiv2::Image *image, Exiv2::TimeValue::Time &time)
|
||||
{
|
||||
Exiv2::IptcData::const_iterator md;
|
||||
bool done = iptc_data(image, "Iptc.Application2.DigitizationTime", md);
|
||||
if(!done)
|
||||
done = iptc_data(image, "Iptc.Application2.TimeCreated", md);
|
||||
if(!done)
|
||||
return false;
|
||||
time = ((Exiv2::TimeValue*)md->getValue().get())->getTime();
|
||||
// Zero is a valid time, so this one is hard to check.
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string iptc_time(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::TimeValue::Time time;
|
||||
if(iptc_get_time(image, time))
|
||||
return str(boost::format("%02d-%02d-%02d") % time.hour % time.minute % time.second);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string iptc_hour(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::TimeValue::Time time;
|
||||
if(iptc_get_time(image, time))
|
||||
return str(boost::format("%02d") % time.hour);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string iptc_minute(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::TimeValue::Time time;
|
||||
if(iptc_get_time(image, time))
|
||||
return str(boost::format("%02d") % time.minute);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string iptc_second(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::TimeValue::Time time;
|
||||
if(iptc_get_time(image, time))
|
||||
return str(boost::format("%02d") % time.second);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string file_time(const Exiv2::Image *, const fs::path &path)
|
||||
{
|
||||
std::tm tm;
|
||||
if(file_get_tm(path, tm))
|
||||
return str(boost::format("%02d-%02d-%02d") % tm.tm_hour % tm.tm_min % tm.tm_sec);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string file_hour(const Exiv2::Image *, const fs::path &path)
|
||||
{
|
||||
std::tm tm;
|
||||
if(file_get_tm(path, tm))
|
||||
return str(boost::format("%02d") % tm.tm_hour);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string file_minute(const Exiv2::Image *, const fs::path &path)
|
||||
{
|
||||
std::tm tm;
|
||||
if(file_get_tm(path, tm))
|
||||
return str(boost::format("%02d") % tm.tm_min);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string file_second(const Exiv2::Image *, const fs::path &path)
|
||||
{
|
||||
std::tm tm;
|
||||
if(file_get_tm(path, tm))
|
||||
return str(boost::format("%02d") % tm.tm_sec);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/*std::string xmp_time(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string xmp_hour(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string xmp_minute(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string xmp_second(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}*/
|
||||
|
||||
std::string exif_dimension(const Exiv2::Image *image, const fs::path &path)
|
||||
{
|
||||
return exif_width(image, path) + "-" + exif_height(image, path);
|
||||
}
|
||||
|
||||
std::string exif_width(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Photo.PixelXDimension", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
std::string exif_height(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Photo.PixelYDimension", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
std::string file_dimension(const Exiv2::Image *image, const fs::path &path)
|
||||
{
|
||||
if(image)
|
||||
return file_width(image, path) + "-" + file_height(image, path);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string file_width(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
if(image)
|
||||
return str(boost::format("%02d") % image->pixelWidth());
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string file_height(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
if(image)
|
||||
return str(boost::format("%02d") % image->pixelHeight());
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
std::string xmp_dimension(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
|
||||
std::string xmp_width(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string xmp_height(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}*/
|
||||
|
||||
std::string exif_model(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Image.Model", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
std::string exif_make(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Image.Make", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
/*std::string xmp_model(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}*/
|
||||
|
||||
std::string exif_speed(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Photo.ShutterSpeedValue", md);
|
||||
if(!done)
|
||||
done = exif_data(image, "Exif.Photo.ExposureTime", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
/*std::string xmp_speed(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}*/
|
||||
|
||||
std::string exif_aperture(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Photo.ApertureValue", md);
|
||||
if(!done)
|
||||
done = exif_data(image, "Exif.Photo.FNumber", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
/*std::string xmp_aperture(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}*/
|
||||
|
||||
std::string exif_focal(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Photo.FocalLength", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
/*std::string xmp_focal(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}*/
|
||||
|
||||
std::string exif_distance(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Photo.SubjectDistance", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
/*std::string xmp_distance(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}*/
|
||||
|
||||
std::string exif_meter(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Photo.MeteringMode", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
std::string exif_macro(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data_easy(image, Exiv2::macroMode, md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
std::string exif_orientation(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data_easy(image, Exiv2::orientation, md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print(), true);
|
||||
}
|
||||
|
||||
std::string exif_lens(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data_easy(image, Exiv2::lensName, md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
|
||||
std::string exif_iso(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data_easy(image, Exiv2::isoSpeed, md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
/*std::string xmp_meter(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}*/
|
||||
|
||||
std::string exif_keyword(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::ExifData::const_iterator md;
|
||||
bool done = exif_data(image, "Exif.Photo.UserComment", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
std::string iptc_keyword(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
Exiv2::IptcData::const_iterator md;
|
||||
bool done = iptc_data(image, "Iptc.Application2.Keywords", md);
|
||||
if(!done)
|
||||
return "";
|
||||
return scrub(md->print());
|
||||
}
|
||||
|
||||
/*std::string xmp_keyword(const Exiv2::Image *image, const fs::path &)
|
||||
{
|
||||
return "";
|
||||
}*/
|
||||
|
@ -1,101 +0,0 @@
|
||||
// ***************************************************************** -*- C++ -*-
|
||||
/*
|
||||
* Copyright (C) 2004-2021 Exiv2 authors
|
||||
* 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.
|
||||
*/
|
||||
// *****************************************************************************
|
||||
|
||||
|
||||
#ifndef HELPERS_HPP_
|
||||
#define HELPERS_HPP_
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#define BOOST_FILESYSTEM_NO_DEPRECATED
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
|
||||
typedef std::string (*pfunc)(const Exiv2::Image *image, const fs::path &path);
|
||||
|
||||
// This would be a lot smaller if Exiv2 had support
|
||||
// for unified metadata
|
||||
|
||||
std::string exif_date(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_year(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_month(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_day(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string iptc_date(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string iptc_year(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string iptc_month(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string iptc_day(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_date(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_year(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_month(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_day(const Exiv2::Image *image, const fs::path &path);
|
||||
/*std::string xmp_date(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string xmp_year(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string xmp_month(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string xmp_day(const Exiv2::Image *image, const fs::path &path);*/
|
||||
std::string exif_time(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_hour(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_minute(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_second(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string iptc_time(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string iptc_hour(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string iptc_minute(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string iptc_second(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_time(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_hour(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_minute(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_second(const Exiv2::Image *image, const fs::path &path);
|
||||
/*std::string xmp_time(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string xmp_hour(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string xmp_minute(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string xmp_second(const Exiv2::Image *image, const fs::path &path);*/
|
||||
std::string exif_dimension(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_width(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_height(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_dimension(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_width(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string file_height(const Exiv2::Image *image, const fs::path &path);
|
||||
/*std::string xmp_dimension(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string xmp_width(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string xmp_height(const Exiv2::Image *image, const fs::path &path);*/
|
||||
std::string exif_model(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_make(const Exiv2::Image *image, const fs::path &path);
|
||||
/*std::string xmp_model(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string xmp_make(const Exiv2::Image *image, const fs::path &path);*/
|
||||
std::string exif_speed(const Exiv2::Image *image, const fs::path &path);
|
||||
//std::string xmp_speed(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_aperture(const Exiv2::Image *image, const fs::path &path);
|
||||
//std::string xmp_aperture(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_focal(const Exiv2::Image *image, const fs::path &path);
|
||||
//std::string xmp_focal(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_distance(const Exiv2::Image *image, const fs::path &path);
|
||||
//std::string xmp_distance(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_meter(const Exiv2::Image *image, const fs::path &path);
|
||||
//std::string xmp_meter(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_macro(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_orientation(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_lens(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_keyword(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string iptc_keyword(const Exiv2::Image *image, const fs::path &path);
|
||||
//std::string xmp_keyword(const Exiv2::Image *image, const fs::path &path);
|
||||
std::string exif_iso(const Exiv2::Image *image, const fs::path &path);
|
||||
|
||||
#endif //HELPERS_HPP_
|
||||
|
@ -1,759 +0,0 @@
|
||||
// ***************************************************************** -*- C++ -*-
|
||||
/*
|
||||
* Copyright (C) 2004-2021 Exiv2 authors
|
||||
* 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.
|
||||
*/
|
||||
// *****************************************************************************
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/array.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <exiv2/image.hpp>
|
||||
#include <exiv2/error.hpp>
|
||||
#include <exiv2/basicio.hpp>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include "MD5.h"
|
||||
#include "helpers.hpp"
|
||||
|
||||
typedef Exiv2::byte md5digest[16];
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
bool g_verbose = false;
|
||||
bool g_neednewline = false;
|
||||
|
||||
// Array size should match number of SLOTs
|
||||
boost::array<int,4> g_run_order = {{-1, -1, -1, -1}};
|
||||
const int EXIF_SLOT = 0;
|
||||
const int IPTC_SLOT = 1;
|
||||
const int XMP_SLOT = 2;
|
||||
const int FILE_SLOT = 3;
|
||||
|
||||
const unsigned DOT_EVERY = 55;
|
||||
|
||||
struct Pattern {
|
||||
std::string pat;
|
||||
std::string desc;
|
||||
pfunc funcs[4]; // order should always be exif, iptc, xmp, file
|
||||
};
|
||||
|
||||
struct PathPart {
|
||||
std::string pre;
|
||||
const Pattern *pat;
|
||||
std::string post;
|
||||
PathPart(std::string pre_, const Pattern *pat_, std::string post_)
|
||||
: pre(pre_), pat(pat_), post(post_) {}
|
||||
};
|
||||
|
||||
std::vector<PathPart> g_path_parts;
|
||||
|
||||
// Instead of making these all global
|
||||
struct ProcessParams {
|
||||
const fs::path &dest_dir;
|
||||
const bool dry_run;
|
||||
const bool ignore_dups;
|
||||
const bool ignore_unsorted;
|
||||
const bool force;
|
||||
const bool rename;
|
||||
const bool symlink;
|
||||
const bool verify;
|
||||
const bool move;
|
||||
const long limit_depth;
|
||||
const fs::path &dups_dir;
|
||||
const fs::path &unsorted_dir;
|
||||
const std::vector<std::string> &excludes;
|
||||
unsigned dups_count;
|
||||
unsigned unsorted_count;
|
||||
unsigned dir_err_count;
|
||||
unsigned file_err_count;
|
||||
unsigned ok_count;
|
||||
unsigned dups_ignored_count;
|
||||
unsigned unsorted_ignored_count;
|
||||
unsigned dir_ex_count;
|
||||
unsigned file_ex_count;
|
||||
};
|
||||
|
||||
void process_directory(const fs::path &directory, const long depth,
|
||||
ProcessParams ¶ms);
|
||||
|
||||
const Pattern g_patterns[] = {
|
||||
{"@date", "date captured (2009-01-19)",
|
||||
{exif_date, iptc_date, NULL, file_date} },
|
||||
{"@year", "year captured (2009)",
|
||||
{exif_year, iptc_year, NULL, file_year} },
|
||||
{"@month", "month captured (01)",
|
||||
{exif_month, iptc_month, NULL, file_month} },
|
||||
{"@day", "day captured (19)",
|
||||
{exif_day, iptc_day, NULL, file_day} },
|
||||
{"@time", "time captured (14-35-27)",
|
||||
{exif_time, iptc_time, NULL, file_time} },
|
||||
{"@hour", "hour captured (14)",
|
||||
{exif_hour, iptc_hour, NULL, file_hour} },
|
||||
{"@min", "minute captured (35)",
|
||||
{exif_minute, iptc_minute, NULL, file_minute} },
|
||||
{"@sec", "second captured (27)",
|
||||
{exif_second, iptc_second, NULL, file_second} },
|
||||
{"@dim", "pixel dimension (2272-1704)",
|
||||
{exif_dimension, NULL, NULL, file_dimension} },
|
||||
{"@x", "pixel width (2272)",
|
||||
{exif_width, NULL, NULL, file_width} },
|
||||
{"@y", "pixel height (1704)",
|
||||
{exif_height, NULL, NULL, file_height} },
|
||||
{"@make", "device make (Canon)",
|
||||
{exif_make, NULL, NULL, NULL} },
|
||||
{"@model", "device model (Canon PowerShot S40)",
|
||||
{exif_model, NULL, NULL, NULL} },
|
||||
{"@speed", "shutter speed (1-60)",
|
||||
{exif_speed, NULL, NULL, NULL} },
|
||||
{"@aper", "aperture (F3.2)",
|
||||
{exif_aperture, NULL, NULL, NULL} },
|
||||
{"@iso", "iso speed (400)",
|
||||
{exif_iso, NULL, NULL, NULL} },
|
||||
{"@focal", "focal length (8.6 mm)",
|
||||
{exif_focal, NULL, NULL, NULL} },
|
||||
{"@dist", "subject distance (1.03 m)",
|
||||
{exif_distance, NULL, NULL, NULL} },
|
||||
{"@meter", "meter mode (multi-segment)",
|
||||
{exif_meter, NULL, NULL, NULL} },
|
||||
{"@macro", "macro mode (Off)",
|
||||
{exif_macro, NULL, NULL, NULL} },
|
||||
{"@orient", "orientation (top_left)",
|
||||
{exif_orientation, NULL, NULL, NULL} },
|
||||
{"@lens", "lens name (Tamron 90mm f-2.8)",
|
||||
{exif_lens, NULL, NULL, NULL} },
|
||||
{"@key", "first keyword (Family)",
|
||||
{exif_keyword, iptc_keyword, NULL, NULL} },
|
||||
|
||||
{"", "", {NULL, NULL, NULL, NULL} }
|
||||
};
|
||||
|
||||
|
||||
// Check that 'opt1' and 'opt2' are not specified at the same time.
|
||||
void conflicting(const po::variables_map& vm,
|
||||
const char* opt1, const char* opt2)
|
||||
{
|
||||
if (vm.count(opt1) && !vm[opt1].defaulted()
|
||||
&& vm.count(opt2) && !vm[opt2].defaulted()) {
|
||||
throw std::logic_error(std::string("conflicting options '")
|
||||
+ opt1 + "' and '" + opt2 + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// Check that 'required' is present
|
||||
void required(const po::variables_map& vm, const char* required)
|
||||
{
|
||||
if (!vm.count(required) || vm[required].defaulted()) {
|
||||
throw std::logic_error(std::string("required parameter '") + required
|
||||
+ "' is missing");
|
||||
}
|
||||
}
|
||||
|
||||
void info(const std::string &msg)
|
||||
{
|
||||
if(g_verbose) {
|
||||
std::cout << msg << "\n";
|
||||
g_neednewline = false;
|
||||
}
|
||||
}
|
||||
|
||||
void error(const std::exception &e, const std::string &msg)
|
||||
{
|
||||
if(g_neednewline) {
|
||||
std::cout << "\n";
|
||||
g_neednewline = false;
|
||||
}
|
||||
std::cerr << e.what() << "\n";
|
||||
std::cerr << msg << std::endl;
|
||||
}
|
||||
|
||||
void usage_header(const char* exname)
|
||||
{
|
||||
std::cout << "Usage: " << exname << " [options] source-dir dest-dir pattern\n";
|
||||
}
|
||||
|
||||
void usage_full(const po::options_description &options, const char* exname)
|
||||
{
|
||||
usage_header(exname);
|
||||
std::cout << "\n Creates groups of files in new directories defined by a metadata 'pattern'.\n" <<
|
||||
" Files are copied, moved, or linked from 'source-dir' to 'dest-dir'.\n" <<
|
||||
" The destination directory should not be within the source directory.\n\n";
|
||||
std::cout << options;
|
||||
|
||||
std::cout << "\nPattern values:\n";
|
||||
for( const Pattern *pattern = g_patterns; pattern->pat.length(); ++pattern) {
|
||||
std::cout << " " << std::setw(8) << std::left << pattern->pat;
|
||||
std::cout << pattern->desc << "\n";
|
||||
}
|
||||
|
||||
std::cout << "\nExamples:\n";
|
||||
std::cout << " `" << exname << " -m mess clean @year-@month'\n";
|
||||
std::cout << " Moves files from 'mess' into directories of 'clean' according to\n" <<
|
||||
" year-month the file was captured (clean/2006-11/...)\n\n";
|
||||
std::cout << " `" << exname << " -o ie source find width-@x/height-@y'\n";
|
||||
std::cout << " Copies files into directories according first to pixel width then pixel\n" <<
|
||||
" height. Check iptc then exif metadata (find/width-2272/height-1704/...)\n\n";
|
||||
std::cout << " `" << exname << " -lf source find @aper/@hour'\n";
|
||||
std::cout << " Force create symlinks in directories according first to aperture then\n" <<
|
||||
" hour captured (find/F3.2/15/...)\n";
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void version()
|
||||
{
|
||||
std::cout << "organized 0.1\n" <<
|
||||
"Copyright (C) 2009 Brad Schick. <schickb@gmail.com>\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" << std::endl;
|
||||
}
|
||||
|
||||
// Returns empty string if the destination subdirectory could not be determined
|
||||
// for the supplied source file.
|
||||
std::string build_dest(const fs::path &source_file)
|
||||
{
|
||||
std::string dest;
|
||||
|
||||
Exiv2::Image::AutoPtr image;
|
||||
try {
|
||||
image = Exiv2::ImageFactory::open(source_file.string());
|
||||
image->readMetadata();
|
||||
}
|
||||
catch(const Exiv2::AnyError&) {
|
||||
// No metadata, let things continue to try file info
|
||||
}
|
||||
|
||||
std::vector<PathPart>::iterator iter = g_path_parts.begin();
|
||||
std::vector<PathPart>::iterator end = g_path_parts.end();
|
||||
for( ; iter != end; ++iter) {
|
||||
dest += iter->pre;
|
||||
std::string result;
|
||||
|
||||
const Pattern *pat = iter->pat;
|
||||
for(unsigned fx = 0; fx < g_run_order.size(); ++fx) {
|
||||
if(g_run_order[fx] != -1 && pat->funcs[g_run_order[fx]]) {
|
||||
if(g_run_order[fx] == FILE_SLOT) {
|
||||
// Always run file operations
|
||||
result = pat->funcs[g_run_order[fx]](image.get(), source_file);
|
||||
}
|
||||
else if(image.get()) {
|
||||
// No point in running metadata operations without an image
|
||||
result = pat->funcs[g_run_order[fx]](image.get(), source_file);
|
||||
}
|
||||
if(result.length())
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If we found no data, even for part of pattern, give up and
|
||||
// return no destination
|
||||
if(!result.length())
|
||||
return result;
|
||||
|
||||
dest += (result + iter->post);
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
bool md5sum(const fs::path &path, md5digest &digest)
|
||||
{
|
||||
try {
|
||||
Exiv2::FileIo io(path.string());
|
||||
if (io.open() != 0)
|
||||
return false;
|
||||
Exiv2::IoCloser closer(io);
|
||||
|
||||
Exiv2::byte buff[4096];
|
||||
MD5_CTX context;
|
||||
MD5Init(&context);
|
||||
|
||||
long read_count = io.read(buff, 4096);
|
||||
while(read_count) {
|
||||
MD5Update(&context, buff, read_count);
|
||||
read_count = io.read(buff, 4096);
|
||||
}
|
||||
MD5Final(digest, &context);
|
||||
return true;
|
||||
}
|
||||
catch (std::exception& ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
po::options_description options("Options");
|
||||
// Don't use default values because the help print it ugly and too wide
|
||||
options.add_options()
|
||||
("move,m", "move files rather than copy")
|
||||
("symlink,s", "symlink files rather than copy (posix only)")
|
||||
("order,o", po::value<std::string>(),
|
||||
"order and types of metadata to read\ne=exif, i=iptc, f=file (default: eif)")
|
||||
("unsorted,u", po::value<std::string>(),
|
||||
"special directory to store unsorted files (default: unsorted)")
|
||||
("dups,d", po::value<std::string>(),
|
||||
"special directory to store files with duplicate names (default: duplicates)")
|
||||
("force,f", "overwrite duplicate files instead of using special directory")
|
||||
("rename,r", "rename duplicate files instead of using special directory")
|
||||
("ignore,i", "ignore both unsorted and duplicate files instead of using special directories")
|
||||
("ignore-unsorted", "ignore unsorted files instead of using special directory")
|
||||
("ignore-dups", "ignore duplicate files instead of using special directory")
|
||||
("verify", "verify copied or moved files and exit if incorrect")
|
||||
("exclude,x", po::value< std::vector<std::string> >(),
|
||||
"exclude directories and files that contain arg (case sensitive on all platforms)")
|
||||
("limit-depth,l", po::value<long>(),
|
||||
"limit recursion to specified depth (0 disables recursion)")
|
||||
("verbose,v", "prints operations as they happen")
|
||||
("dry-run,n", "do not make actual changes (implies verbose)")
|
||||
("help,h", "show this help message then exit")
|
||||
("version,V", "show program version then exit")
|
||||
;
|
||||
|
||||
po::options_description hidden("Hidden Options");
|
||||
hidden.add_options()
|
||||
("source-dir", po::value< std::string >(), "directory of files to organize, may end in file wildcard")
|
||||
("dest-dir", po::value< std::string >(), "designation directory for files, may not be within source-dir")
|
||||
("pattern", po::value< std::string >(), "subdirectory pattern for grouping files within dest-dir")
|
||||
;
|
||||
|
||||
po::options_description cmdline;
|
||||
cmdline.add(options).add(hidden);
|
||||
|
||||
po::positional_options_description positional;
|
||||
positional.add("source-dir", 1);
|
||||
positional.add("dest-dir", 1);
|
||||
positional.add("pattern", 1);
|
||||
|
||||
try {
|
||||
po::variables_map vm;
|
||||
po::store(po::command_line_parser(argc, argv).
|
||||
options(cmdline).positional(positional).run(), vm);
|
||||
po::notify(vm);
|
||||
|
||||
if (vm.count("help")) {
|
||||
usage_full(options, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (vm.count("version")) {
|
||||
version();
|
||||
return 0;
|
||||
}
|
||||
|
||||
conflicting(vm, "verify", "symlink");
|
||||
conflicting(vm, "move", "symlink");
|
||||
conflicting(vm, "unsorted", "ignore");
|
||||
conflicting(vm, "unsorted", "ignore-unsorted");
|
||||
conflicting(vm, "dups", "ignore");
|
||||
conflicting(vm, "dups", "ignore-dups");
|
||||
conflicting(vm, "force", "ignore");
|
||||
conflicting(vm, "force", "ignore-dups");
|
||||
conflicting(vm, "force", "rename");
|
||||
conflicting(vm, "rename", "ignore");
|
||||
conflicting(vm, "rename", "ignore-dups");
|
||||
required(vm, "source-dir");
|
||||
required(vm, "dest-dir");
|
||||
required(vm, "pattern");
|
||||
|
||||
const bool dry_run = vm.count("dry-run") != 0;
|
||||
g_verbose = (vm.count("verbose") != 0 || dry_run);
|
||||
|
||||
std::string order = "eif";
|
||||
if(vm.count("order")) {
|
||||
order = vm["order"].as<std::string>();
|
||||
|
||||
boost::to_lower(order);
|
||||
if(order.length() > 3) {
|
||||
throw std::logic_error(std::string("order is longer than 4 characters"));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned i = 0;
|
||||
std::string::iterator end = order.end();
|
||||
for(std::string::iterator iter = order.begin(); iter != end && i < 4; ++iter, ++i) {
|
||||
switch(*iter) {
|
||||
case 'e':
|
||||
g_run_order[i] = EXIF_SLOT;
|
||||
break;
|
||||
case 'i':
|
||||
g_run_order[i] = IPTC_SLOT;
|
||||
break;
|
||||
case 'x':
|
||||
throw std::logic_error(std::string("xmp not implemented yet '") +
|
||||
*iter + "'");
|
||||
break;
|
||||
case 'f':
|
||||
g_run_order[i] = FILE_SLOT;
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error(std::string("unknown order character '") +
|
||||
*iter + "'");
|
||||
}
|
||||
}
|
||||
|
||||
const fs::path source_dir( vm["source-dir"].as<std::string>() );
|
||||
if( !exists(source_dir) || !is_directory(source_dir) ) {
|
||||
throw std::logic_error(std::string("source '") +
|
||||
source_dir.string() + "' must exist and be a directory");
|
||||
}
|
||||
|
||||
const fs::path dest_dir( vm["dest-dir"].as<std::string>() );
|
||||
if( exists(dest_dir) && !is_directory(dest_dir) ) {
|
||||
throw std::logic_error(std::string("destination '") +
|
||||
dest_dir.string() + "' must be a directory");
|
||||
}
|
||||
|
||||
// Boost doesn't seem to have a way to get a canonical path, so this
|
||||
// simple test is easy to confuse with some ../../'s in the paths. Oh
|
||||
// well, this is good enough for now.
|
||||
fs::path test_dest(dest_dir);
|
||||
for(; !test_dest.empty(); test_dest = test_dest.parent_path()) {
|
||||
if(fs::equivalent(source_dir, test_dest)) {
|
||||
throw std::logic_error(std::string("dest-dir must not be within source-dir"));
|
||||
}
|
||||
}
|
||||
|
||||
// Disect the pattern
|
||||
std::string pattern = vm["pattern"].as<std::string>();
|
||||
boost::regex regex( "([^@]*)(@[[:alpha:]]+)([^@]*)");
|
||||
boost::sregex_iterator m_iter = make_regex_iterator(pattern, regex);
|
||||
boost::sregex_iterator m_end;
|
||||
for( ; m_iter != m_end; ++m_iter) {
|
||||
const boost::smatch &match = *m_iter;
|
||||
const std::string &pre = match[1];
|
||||
const std::string &pat = match[2];
|
||||
const std::string &post = match[3];
|
||||
|
||||
// Should put this in a map, but there aren't that many options now
|
||||
bool found = false;
|
||||
for( const Pattern *pattern = g_patterns; pattern->pat.length(); ++pattern) {
|
||||
if(pattern->pat == pat) {
|
||||
PathPart part(pre, pattern, post);
|
||||
g_path_parts.push_back(part);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) {
|
||||
throw std::logic_error(std::string("unknown pattern '") + pat + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// Assign defaults to params that need them
|
||||
const bool ignore = vm.count("ignore") != 0;
|
||||
std::vector<std::string> excludes;
|
||||
if(vm.count("exclude"))
|
||||
excludes = vm["exclude"].as< std::vector<std::string> >();
|
||||
long limit_depth = LONG_MAX;
|
||||
if(vm.count("limit-depth")) {
|
||||
limit_depth = vm["limit-depth"].as<long>();
|
||||
// Boost program_options doesn't work with unsigned, so do it manually
|
||||
if( limit_depth < 0 )
|
||||
throw std::logic_error(std::string("recursion depth limit must be positive"));
|
||||
}
|
||||
std::string dups = "duplicates";
|
||||
if(vm.count("dups"))
|
||||
dups = vm["dups"].as<std::string>();
|
||||
const fs::path dups_dir = dest_dir / dups;
|
||||
|
||||
std::string unsorted = "unsorted";
|
||||
if(vm.count("unsorted"))
|
||||
unsorted = vm["unsorted"].as<std::string>();
|
||||
const fs::path unsorted_dir = dest_dir / unsorted;
|
||||
|
||||
ProcessParams params = {
|
||||
dest_dir,
|
||||
dry_run,
|
||||
(vm.count("ignore-dups") != 0 || ignore),
|
||||
(vm.count("ignore-unsorted") != 0 || ignore),
|
||||
vm.count("force") != 0,
|
||||
vm.count("rename") != 0,
|
||||
vm.count("symlink") != 0,
|
||||
vm.count("verify") != 0,
|
||||
vm.count("move") != 0,
|
||||
limit_depth,
|
||||
dups_dir,
|
||||
unsorted_dir,
|
||||
excludes,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
process_directory(source_dir, 0, params);
|
||||
|
||||
std::string op = "copied";
|
||||
if(params.symlink)
|
||||
op = "linked";
|
||||
else if(params.move)
|
||||
op = "moved";
|
||||
|
||||
if(dry_run)
|
||||
op = std::string("would be ") + op;
|
||||
|
||||
if(g_neednewline)
|
||||
std::cout << "\n";
|
||||
|
||||
std::cout << "\n" << params.ok_count << " files " << op << "\n";
|
||||
std::cout << " " << params.dups_count << " duplicates\n";
|
||||
std::cout << " " << params.unsorted_count << " unsorted\n";
|
||||
if(params.dups_ignored_count)
|
||||
std::cout << params.dups_ignored_count << " duplicates ignored\n";
|
||||
if(params.unsorted_ignored_count)
|
||||
std::cout << params.unsorted_ignored_count << " unsorted ignored\n";
|
||||
if(params.dir_ex_count)
|
||||
std::cout << params.dir_ex_count << " directories excluded\n";
|
||||
if(params.file_ex_count)
|
||||
std::cout << params.file_ex_count << " files excluded\n";
|
||||
if(params.dir_err_count)
|
||||
std::cout << params.dir_err_count << " directory errors\n";
|
||||
if(params.file_err_count)
|
||||
std::cout << params.file_err_count << " file errors\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exiv2::AnyError& e) {
|
||||
error(e, std::string("Aborting"));
|
||||
return -1;
|
||||
}
|
||||
catch(std::logic_error& e) {
|
||||
error(e, "");
|
||||
usage_header(argv[0]);
|
||||
std::cout << argv[0] << " -h for more help" << std::endl;
|
||||
return -2;
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
error(e, "Aborting");
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
|
||||
boost::regex uregex("(.*?)\\(([[:digit:]]{1,2})\\)$");
|
||||
|
||||
fs::path uniquify(const fs::path &dest)
|
||||
{
|
||||
std::string ext = dest.extension().string();
|
||||
std::string fname = dest.stem().string();
|
||||
fs::path parent = dest.parent_path();
|
||||
|
||||
unsigned number = 1;
|
||||
std::string newfname;
|
||||
fs::path newdest;
|
||||
|
||||
boost::smatch match;
|
||||
if(boost::regex_search(fname, match, uregex)) {
|
||||
// Matches are indexes into fname, so don't change it while reading values
|
||||
newfname = match[1];
|
||||
number = boost::lexical_cast<short>(match[2]);
|
||||
fname = newfname;
|
||||
}
|
||||
|
||||
do {
|
||||
newfname = fname + "(" + boost::lexical_cast<std::string>(++number) + ")" + ext;
|
||||
newdest = parent / newfname;
|
||||
} while(fs::exists(newdest));
|
||||
|
||||
return newdest;
|
||||
}
|
||||
|
||||
void process_directory(const fs::path &directory, const long depth,
|
||||
ProcessParams ¶ms)
|
||||
{
|
||||
// Exclude entire directories
|
||||
bool exclude = false;
|
||||
std::vector<std::string>::const_iterator x_iter = params.excludes.begin();
|
||||
std::vector<std::string>::const_iterator x_end = params.excludes.end();
|
||||
for( ; x_iter != x_end; ++x_iter ) {
|
||||
if(boost::contains(directory.string(), *x_iter)) {
|
||||
exclude = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(exclude) {
|
||||
info(std::string("excluding directory: ") + directory.string() +
|
||||
" matched: " + *x_iter);
|
||||
++params.dir_ex_count;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
fs::directory_iterator p_iter(directory), p_end;
|
||||
for( ; p_iter != p_end; ++p_iter) {
|
||||
if( is_directory(*p_iter) ) {
|
||||
// recurse if we haven't hit the limit
|
||||
if(depth < params.limit_depth)
|
||||
process_directory(p_iter->path(), depth + 1, params);
|
||||
else {
|
||||
info(std::string("depth reached, skipping: ") +
|
||||
p_iter->path().string());
|
||||
}
|
||||
}
|
||||
else if( is_regular_file(*p_iter) ) {
|
||||
|
||||
// Check again for excluding file names
|
||||
exclude = false;
|
||||
x_iter = params.excludes.begin();
|
||||
for( ; x_iter != x_end; ++x_iter ) {
|
||||
if(boost::contains(p_iter->path().string(), *x_iter)) {
|
||||
exclude = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(exclude) {
|
||||
info(std::string("excluding file: ") + p_iter->path().string() +
|
||||
" matched: " + *x_iter);
|
||||
++params.file_ex_count;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const fs::path dest_subdir = build_dest(*p_iter);
|
||||
fs::path dest_file;
|
||||
if(!dest_subdir.empty())
|
||||
dest_file = params.dest_dir / dest_subdir;
|
||||
else if(params.ignore_unsorted) {
|
||||
info(std::string("ignoring unsorted: ") + p_iter->path().string());
|
||||
++params.unsorted_ignored_count;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
info(std::string("unsorted file (missing metadata): ") + p_iter->path().string());
|
||||
dest_file = params.unsorted_dir;
|
||||
++params.unsorted_count;
|
||||
}
|
||||
|
||||
dest_file /= p_iter->path().filename();
|
||||
|
||||
if(fs::exists(dest_file)) {
|
||||
if(params.ignore_dups) {
|
||||
info(std::string("ignoring: ") + p_iter->path().string() +
|
||||
" duplicates: " + dest_file.string());
|
||||
++params.dups_ignored_count;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
if(params.force) {
|
||||
info(std::string("force removing: ") + dest_file.string() + " for: "
|
||||
+ p_iter->path().string());
|
||||
if(!params.dry_run)
|
||||
fs::remove(dest_file);
|
||||
}
|
||||
else if(params.rename) {
|
||||
info(std::string("renaming: ") + p_iter->path().string() +
|
||||
" duplicates: " + dest_file.string());
|
||||
dest_file = uniquify(dest_file);
|
||||
}
|
||||
else {
|
||||
info(std::string("duplicate file: ") + p_iter->path().string() +
|
||||
" of: " + dest_file.string());
|
||||
dest_file = params.dups_dir / dest_subdir / p_iter->path().filename();
|
||||
// Ugh, more dup possibilities
|
||||
if(fs::exists(dest_file)) {
|
||||
info(std::string("renaming: ") + p_iter->path().string() +
|
||||
" duplicates: " + dest_file.string());
|
||||
dest_file = uniquify(dest_file);
|
||||
}
|
||||
}
|
||||
++params.dups_count;
|
||||
}
|
||||
}
|
||||
|
||||
if(!params.dry_run)
|
||||
fs::create_directories(dest_file.parent_path());
|
||||
|
||||
if(params.symlink) {
|
||||
info(std::string("linking from: ") + p_iter->path().string() +
|
||||
" to: " + dest_file.string());
|
||||
if(!params.dry_run) {
|
||||
// The target of a symlink must be either absolute (aka complete) or
|
||||
// relative to the location of the link. Easiest solution is to make
|
||||
// a complete path.
|
||||
fs::path target;
|
||||
if(p_iter->path().is_complete())
|
||||
target = p_iter->path();
|
||||
else
|
||||
target = fs::initial_path() / p_iter->path();
|
||||
fs::create_symlink(target, dest_file);
|
||||
}
|
||||
}
|
||||
else {
|
||||
info(std::string("copying from: ") + p_iter->path().string() +
|
||||
" to: " + dest_file.string());
|
||||
if(!params.dry_run) {
|
||||
// Copy the file and restore its write time (needed for posix)
|
||||
std::time_t time = fs::last_write_time(*p_iter);
|
||||
fs::copy_file(*p_iter, dest_file);
|
||||
fs::last_write_time(dest_file, time);
|
||||
if(params.verify) {
|
||||
md5digest src_digest, dst_digest;
|
||||
bool ok = md5sum(p_iter->path(), src_digest);
|
||||
if(ok)
|
||||
ok = md5sum(dest_file, dst_digest);
|
||||
if(ok)
|
||||
ok = (memcmp(src_digest,dst_digest, sizeof(md5digest))==0);
|
||||
if(!ok) {
|
||||
// Should probably find a more appropriate exception for this
|
||||
throw std::runtime_error(std::string("File verification failed: '")
|
||||
+ p_iter->path().string() + "' differs from '" +
|
||||
dest_file.string() + "'");
|
||||
}
|
||||
else {
|
||||
info(std::string("verification passed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(params.move) {
|
||||
info(std::string("removing: ") + p_iter->path().string());
|
||||
if(!params.dry_run)
|
||||
fs::remove(*p_iter);
|
||||
}
|
||||
|
||||
if(!g_verbose && (params.ok_count % DOT_EVERY)==0) {
|
||||
std::cout << "." << std::flush;
|
||||
g_neednewline = true;
|
||||
}
|
||||
++params.ok_count;
|
||||
}
|
||||
catch(fs::filesystem_error& e) {
|
||||
error(e, std::string("skipping file: " + p_iter->path().string()));
|
||||
++params.file_err_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(fs::filesystem_error& e) {
|
||||
error(e, std::string("skipping directory: " + directory.string()));
|
||||
++params.dir_err_count;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue