Skip to content

Commit

Permalink
updated exiv to match more tags
Browse files Browse the repository at this point in the history
  • Loading branch information
meitham committed Dec 26, 2012
1 parent d7dca60 commit 6631630
Showing 1 changed file with 99 additions and 26 deletions.
125 changes: 99 additions & 26 deletions exivfind.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,28 @@
or you could combine long tags together such as
"""
import fnmatch
import argparse
import fnmatch
import os
import subprocess
import traceback
import pyexiv2
from functools import partial
from collections import OrderedDict
from functools32 import lru_cache

try:
import ipdb as pdb
except ImportError:
import pdb

import pyexiv2
from functools32 import lru_cache

exiv_tags = {
'make': 'Exif.Image.Make',
'model': 'Exif.Image.Model',
'software': 'Exif.Image.Software',
}


class TreeWalker:
"""provides a functionality similar to os.walk but can do
Expand Down Expand Up @@ -97,81 +107,144 @@ def name_match(fpath, fname, *args, **kwargs):
return fnmatch.fnmatch(fname, pattern)


def make(fpath, fname, *args, **kwargs):
"""Checks whether the exiv image maker matches a given string
def tag_match(fpath, fname, *args, **kwargs):
"""Matches an exiv tag from a file against a porposed one from a user
"""
manufacturer = kwargs['make']
metadata = read_exiv(fpath, fname)
verbosity = kwargs.get('verbosity', 0)
case_match = kwargs.get('case_sensitive', True)
user_tag = kwargs['tag']
user_tag_value = kwargs['user_tag_value']
exiv_tag = exiv_tags[user_tag]
metadata = read_exiv(fpath, fname, verbosity)
if metadata is None:
return False
try:
return metadata['Exif.Image.Make'].value == manufacturer
except KeyError: # make is not available
exiv_tag_value = metadata[exiv_tag].value
if verbosity > 2:
print("%(exiv_tag)s: %(exiv_tag_value)s" % locals())
if case_match:
return user_tag_value == exiv_tag_value
return user_tag_value.lower() == exiv_tag_value.lower()
except KeyError: # tag is not available
if verbosity > 2:
traceback.print_exc()
return None


def act_print(fpath, fname, *args, **kwargs):
print(os.path.join(fpath, fname))


def act_exec(fpath, fname, *args, **kwargs):
path = os.path.join(fpath, fname)
action = kwargs['exec']
action = [path if t == '{}' else t for t in action]
#print(' '.join(action))
subprocess.call(action[:-1])


tests = {
'name': name_match,
'make': make,
# 'imake': partial(make, case_sensitive=False),
'make': partial(tag_match, tag='make'),
'imake': partial(tag_match, tag='make', case_sensitive=False),
# 'rmake': rmake,
# 'orientation': orientation,
# 'software': software,
'software': partial(tag_match, tag='software'),
# 'date-time': exiv_datetime,
# 'date-time-newer': exiv_datetime_newer,
# 'compression': compression,
# 'x-resolution': x_resolution, # accepts expressions e.g. ">3000"
}


actions = {
'print': act_print,
'exec': act_exec,
}


class TestAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if not 'tests' in namespace:
setattr(namespace, 'tests', OrderedDict())
namespace.tests[self.dest] = values


class ActionAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if not 'actions' in namespace:
setattr(namespace, 'actions', [])
namespace.actions.append((self.dest, values))


def parse_args():
"""
"""
class PreserveOrderAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if not 'ordered_args' in namespace:
setattr(namespace, 'ordered_args', OrderedDict())
namespace.ordered_args[self.dest] = values

parser = argparse.ArgumentParser(description="extensible pure python "
"gnu file like tool.")
parser = argparse.ArgumentParser()
parser.add_argument('path', action='store', nargs='?', default=os.getcwd())
parser.add_argument('-name', dest='name', action=PreserveOrderAction)
parser.add_argument('-make', dest='make', action=PreserveOrderAction)
parser.add_argument('--verbose', '-v', action='count')
parser.add_argument('-name', dest='name', action=TestAction)
parser.add_argument('-make', dest='make', action=TestAction)
parser.add_argument('-imake', dest='imake', action=TestAction)
parser.add_argument('-true', dest='true', action=TestAction, nargs=0)
parser.add_argument('-print', dest='print', action=ActionAction, nargs=0)
parser.add_argument('-exec', dest='exec', action=ActionAction, nargs='+')
return parser.parse_args()


def evaluate(fpath, fname, args):
result = True
for filter_name, values in args.ordered_args.iteritems():
"""Evaluates a user test and return True or False, like GNU find tests
"""
args_tests = getattr(args, 'tests', {})
for filter_name, values in args_tests.iteritems():
if values is None:
continue # unused test
#if filter_name not in tests:
# continue # not all provided options are filters
filter_func = tests[filter_name]
if not filter_func(fpath, fname, **{filter_name: values}):
if not filter_func(fpath, fname, **{
'user_tag_value': values,
'verbosity': args.verbose}):
return False
return True


def act_on(fpath, fname, args):
"""Applies an action on a file, like GNU find action
"""
args_actions = getattr(args, 'actions', [])
if not args_actions:
act_print(fpath, fname)
for action, options in args_actions:
if action in actions:
func = actions[action]
func(fpath, fname, **{action: options})


@lru_cache(maxsize=128)
def read_exiv(fpath, fname):
def read_exiv(fpath, fname, verbosity=0):
"""Returns an EXIF metadata from a file
"""
path = os.path.join(fpath, fname)
try:
metadata = pyexiv2.ImageMetadata(path)
metadata.read()
return metadata
except(IOError, UnicodeDecodeError) as e:
traceback.print_exc()
if verbosity > 1:
traceback.print_exc()
return None


def main():
args = parse_args()
tw = TreeWalker(top=args.path)
for fpath, fname in tw.walk():
if not evaluate(fpath, fname, args):
continue
print os.path.join(fpath, fname)
act_on(fpath, fname, args)


if __name__ == '__main__':
Expand Down

0 comments on commit 6631630

Please sign in to comment.