-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmfindexif.py
267 lines (236 loc) · 9.17 KB
/
mfindexif.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
#!/usr/bin/env python
"""
TODO:
1. Enable tag aliases outside code, such as .mfindrc that contains
a mapping similar to the exif_tags below.
2. provide more tests such as
a. -has-tag "tag_name"
b. -ihas-tag "tag_name" # case insensetive
c. -rhas-tag "tag_name" # regex
The tags above can optional take a value
3. Supports files as arguments.
"""
from __future__ import print_function
import fnmatch
import hashlib
import logging
import traceback
import pyexiv2
from mfinder import Primary, ArgTest, ArgAction
logger = logging.getLogger('mfind')
exif_tags = {
'make': 'Exif.Image.Make',
'model': 'Exif.Image.Model',
'software': 'Exif.Image.Software',
}
def match_tag(context, tag_name, tag_value, case_sensitive=True):
"""Matches an exif tag from a file against a porposed one from a user
"""
path = context['path']
verbosity = context.get('verbosity', 0)
exif_tag = exif_tags.get(tag_name, tag_name)
try:
metadata = context['exif.metadata']
except KeyError:
metadata = read_exif(path, verbosity)
context['exif.metadata'] = metadata
if metadata is None:
return
try:
exif_tag_value = metadata[exif_tag].value
if not case_sensitive:
tag_value = tag_value.lower()
exif_tag_value = exif_tag_value.lower()
if tag_value == exif_tag_value:
return context
return
except KeyError: # tag is not available
if verbosity > 2:
traceback.print_exc()
class TagMatchPrimary(Primary):
"""Matches an exif tag from a file against a porposed one from a user
"""
def __call__(self, context):
logger.debug(context)
path = context['path']
tag_name = context['args'][0]
tag_value = context['args'][1]
verbosity = context.get('verbosity', 0)
exif_tag = exif_tags.get(tag_name, tag_name)
try:
metadata = context['exif.metadata']
except KeyError:
metadata = read_exif(path, verbosity)
context['exif.metadata'] = metadata
if metadata is None:
return
try:
exif_tag_value = metadata[exif_tag].value
if not self.case_sensitive:
tag_value = tag_value.lower()
exif_tag_value = exif_tag_value.lower()
if tag_value == exif_tag_value:
return context
return
except KeyError: # tag is not available
if verbosity > 2:
traceback.print_exc()
class PrintBufferHashPrimary(Primary):
"""Print a hash of the main image buffer
"""
def __call__(self, context):
path = context['path']
verbosity = context.get('verbosity', 0)
try:
metadata = context['metadata']
except KeyError:
metadata = read_exif(path, verbosity)
context['exif.metadata'] = metadata
if metadata is None:
return context
h = hashlib.sha256()
h.update(metadata.buffer)
digest = h.hexdigest()
context['buffer'].append(digest)
return context
class PrintTagPrimary(Primary):
"""Print a tag by name
Always return context
"""
def __call__(self, context):
path = context['path']
tag_name = context['args']
verbosity = context.get('verbosity', 0)
tag_name = exif_tags.get(tag_name, tag_name)
try:
m = context['metadata']
except KeyError:
m = read_exif(path, verbosity)
context['exif.metadata'] = m
if m is None:
return context
try:
exif_tag_value = m[tag_name].raw_value
context['buffer'].append(exif_tag_value)
return context
except KeyError:
pass
tags = [m[k].raw_value for k in fnmatch.filter(m.exif_keys, tag_name)]
if not tags:
return context
context['buffer'].append('\n'.join(tags))
return context
class PrintTagsPrimary(Primary):
"""Print all tags available in an image
Always return context
"""
def __call__(self, context):
path = context['path']
tag_name = context.get('args', None)
verbosity = context.get('verbosity', 0)
try:
m = context['metadata']
except KeyError:
m = read_exif(path, verbosity)
context['exif.metadata'] = m
if m is None:
return context
if tag_name is not None:
tags = [(k, m[k].raw_value)
for k in fnmatch.filter(m.exif_keys, tag_name)]
else:
tags = [(k, m[k].raw_value) for k in m.exif_keys]
pairs = ["%(k)s: %(v)s" % {'k': k, 'v': v} for k, v in tags]
context['buffer'].append('\n'.join(pairs))
return context
tests = {
'tag': lambda context: match_tag(context,
tag_name=context['args'][0],
tag_value=context['args'][1],
),
'make': lambda context: match_tag(context, tag_name='make',
tag_value=context['args'],
case_sensitive=True),
'imake': lambda context: match_tag(context, tag_name='make',
tag_value=context['args'],
case_sensitive=False),
'model': TagMatchPrimary(case_sensitive=True, tag_name='model'),
'imodel': TagMatchPrimary(case_sensitive=False, tag_name='imodel'),
'software': TagMatchPrimary(case_sensitive=True, tag_name='software'),
'isoftware': TagMatchPrimary(case_sensitive=False, tag_name='software'),
# 'orientation': orientation,
# 'date-time': exif_datetime,
# 'date-time-newer': exif_datetime_newer,
# 'compression': compression,
# 'x-resolution': x_resolution, # accepts expressions e.g. ">3000"}
}
actions = {
'print_tag': PrintTagPrimary(),
'print_tags': PrintTagsPrimary(),
'print_buffer_hash': PrintBufferHashPrimary(),
}
def cli_args(parser):
"""This will be called by the main cli_args() from mfind
"""
parser.add_argument('-tag', dest='tag', action=ArgTest,
nargs=2,
help="""Filter images by a tag and its value
""")
parser.add_argument('-make', dest='make', action=ArgTest,
help="""Filter images by their camera manufacterer name.
e.g. `-make Canon`
would only match images where Exif.Image.Make is "Canon"
""")
parser.add_argument('-imake', dest='imake', action=ArgTest,
help="""Filter images by their camera manufacterer name.
similar to `-make` except this match is case insensitive
e.g. `-imake canon`
would match images where Exif.Image.Make is "Canon" or "CaNoN"
""")
parser.add_argument('-model', dest='model', action=ArgTest,
help="""Filter images by their camera manufacterer model.
""")
parser.add_argument('-imodel', dest='imodel', action=ArgTest,
help="""Filter images by their camera manufacterer model.
This match is case insensitive.
""")
parser.add_argument('-software', dest='software', action=ArgTest,
help="""Filter images by Exif.Image.Software value.
""")
parser.add_argument('-isoftware', dest='isoftware', action=ArgTest,
help="""Filter images by Exif.Image.Software value.
This match is case insensitive.
""")
parser.add_argument('-print-buffer-hash', dest='print_buffer_hash',
action=ArgAction, nargs=0, help="""Print a hash of the image
buffer.
""")
parser.add_argument('-print-tag', dest='print_tag', action=ArgAction,
help="""Print a tag given by name.
e.g. `-print-tag "Exif.Thumbnail.Orientation"`
You could also use short version of the tag,
e.g. `-print-tag "make"` would resolve to "Exif.Image.Make"
The argument here could be a pattern (follows unix globbing
patterns) so you could say
`-print-tag '*Image*'` and that would match any tag that contains
the work "Image", case sensitive.
""")
parser.add_argument('-print-tags', dest='print_tags',
action=ArgAction, nargs='?',
help="""Print a tag given by name.
similar to -print-tag except this would print the actual tag name
before the tag value, separated by a colon.
The argument here is optional and if no argument provided then it
would print all tags available in the image metadata.
""")
return parser
def read_exif(path, verbosity=0):
"""Returns an EXIF metadata from a file
"""
try:
metadata = pyexiv2.ImageMetadata(path)
metadata.read()
return metadata
except(IOError, UnicodeDecodeError):
if verbosity > 0:
traceback.print_exc()