"""
Part of the tagit module.
A copy of the license is provided with the project.
Author: Matthias Baumgartner, 2016
"""
# STANDARD IMPORTS
import operator
import datetime
import os
import magic
import os.path
import re
# INNER-MODULE IMPORTS
from ..basics import unique
# EXPORTS
__all__ = ('Extractor_Aggregator_all', 'Extractor_Aggregator', 'Extractor_Constant', 'Extractor_Date', 'Extractor_Photometrics', 'Extractor_Path')
## CODE ##
[docs]class Extractor(object):
[docs] def tags(self, image, settings, meta_adapter): # FIXME: Should we move settings, meta_adapter to the constructor?
"""Return a list of automatically extracted tags from *image*."""
raise NotImplementedError()
[docs]class Extractor_Constant(Extractor):
"""Constant information, user-provided."""
def tags(self, image, settings, meta_adapter):
return settings.trace('extractor', 'constant', [])
[docs]class Extractor_Date(Extractor):
"""
Date information:
* Year : Year (4 digit)
* Day : Day of month
* Month : Month (int 1-12 or name Jan-Dec)
* Weekday : Weekday (int 1-7 (iso) or name Mon-Sun)
* Hour : Hour (int)
* Minute : Minute (int)
* Day/Night : day or night
"""
def tags(self, image, settings, meta_adapter):
tags = []
# get the date
# We could optimize this as in Extractor_photometrics, but it's probably not worth the effort.
date = None
if settings.trace('extractor', 'date', 'use_exif', False):
date = meta_adapter.get_date(image)
if date is None:
stat = os.stat(image)
date = datetime.datetime.fromtimestamp(min(stat.st_ctime, stat.st_mtime))
# get the tags
if settings.trace('extractor', 'date', 'year', False):
tags.append(str(date.year))
if settings.trace('extractor', 'date', 'day', False):
tags.append(str(date.day))
if settings.trace('extractor', 'date', 'month_lit', False):
tags.append(date.strftime('%B').lower())
if settings.trace('extractor', 'date', 'month_num', False):
tags.append(str(date.month))
if settings.trace('extractor', 'date', 'weekday_lit', False):
tags.append(date.strftime('%A').lower())
if settings.trace('extractor', 'date', 'weekday_num', False):
tags.append(str(date.isoweekday()))
if settings.trace('extractor', 'date', 'hour', False):
tags.append(str(date.hour))
if settings.trace('extractor', 'date', 'minute', False):
tags.append(str(date.minute))
if settings.trace('extractor', 'date', 'day_night', False):
state = (date.hour > 6 and date.hour <= 18) and 'day' or 'night'
tags.append(state)
return tags
[docs]class Extractor_Photometrics(Extractor):
"""
Image and camera metrics:
* Resolution : Image resolution (width, height)
* Exposure : Exposure time (float, seconds, inverted)
* Focal length : Reported focal length or 35mm equivalent (float, mm)
* Aperture : Aperture ('F' + float)
* ISO : ISO level (int)
"""
def tags(self, image, settings, meta_adapter):
metrics = None
get_metrics = lambda: metrics or meta_adapter.get_metrics(image)
tags = []
if settings.trace('extractor', 'photometric', 'orientation', False):
width, height, orientation = meta_adapter.get_dimensions(image)
if orientation > 0 and width > 0 and height > 0:
ori = (width >= height) and 'landscape' or 'portrait' # Case for orientation <= 4
if orientation > 4:
ori = (ori == 'landscape') and 'portrait' or 'landscape' # Switch
tags.append(ori)
if settings.trace('extractor', 'photometric', 'exposure', False):
metrics = get_metrics()
if 'exposure' in metrics and metrics['exposure'] > 0: tags.append('1/' + str(int(1.0 / metrics['exposure'])))
if settings.trace('extractor', 'photometric', 'focal_length', False):
metrics = get_metrics()
if 'focal_length' in metrics and metrics['focal_length'] > 0: tags.append(str(metrics['focal_length']) + ' mm')
if settings.trace('extractor', 'photometric', 'focal_length_35', False):
metrics = get_metrics()
if 'focal_length_35' in metrics and metrics['focal_length_35'] > 0: tags.append(str(metrics['focal_length_35']) + ' mp')
if settings.trace('extractor', 'photometric', 'aperture', False):
metrics = get_metrics()
if 'aperture' in metrics and metrics['aperture'] > 0: tags.append('F' + str(metrics['aperture']))
if settings.trace('extractor', 'photometric', 'iso', False):
metrics = get_metrics()
if 'iso' in metrics and metrics['iso'] > 0: tags.append('ISO ' + str(metrics['iso']))
if settings.trace('extractor', 'photometric', 'flash', False):
metrics = get_metrics()
if 'flash' in metrics and metrics['flash'] == True: tags.append('flash')
return tags
[docs]class Extractor_Path(Extractor):
"""
Path information:
* mime : Mime type (image/jpeg, ...)
* extension : File extension (jpg, png, ...)
* dir_after : Folder after specified path
* dir_last : Image's parent directory
* dir_after_call: Folder after search root
strip_date: Remove leading or trailing date information
"""
def tags(self, image, settings, meta_adapter):
tags = []
if settings.trace('extractor', 'path', 'mime', False):
tags.append(magic.from_file(image, mime=True))
if settings.trace('extractor', 'path', 'extension', False):
root, ext = os.path.splitext(image)
ext = ext.lstrip('.').lower()
if len(ext) > 0: tags.append(ext)
if settings.trace('extractor', 'path', 'dir_last', False):
name = os.path.basename(os.path.dirname(image))
if len(name) > 0: tags.append(name)
if settings.trace('extractor', 'path', 'dir_after', []) != []:
for dir_ in settings.trace('extractor', 'path', 'dir_after', []):
dir_ = os.path.normpath(dir_)
if image.startswith(dir_):
if dir_ == os.path.sep:
path = image[len(dir_):]
else:
path = image[len(dir_) + len(os.path.sep):]
path = path[:path.find(os.path.sep)]
path = path.lower()
if settings.trace('extractor', 'path', 'strip_date', False):
path = re.match('[\s\d\-_]*(\w+)[\s\d\-_]*', path).groups()[0]
tags.append(path)
return tags
[docs]class Extractor_Aggregator(Extractor):
"""Combine multiple *Extractor* instances into one."""
def __init__(self, children=None):
self.children = children or []
[docs] def add_child(self, child):
"""Add another *Extractor* instance."""
if child not in self.children:
self.children.append(child)
return self
def tags(self, image, settings, meta_adapter):
tags = [child.tags(image, settings, meta_adapter) for child in self.children]
tags = reduce(operator.add, tags, [])
tags = unique(tags)
return tags
[docs]class Extractor_Aggregator_all(Extractor_Aggregator):
"""Aggregate all known *Extractors*."""
def __init__(self, children=None):
super(Extractor_Aggregator_all, self).__init__(children)
self.add_child(Extractor_Constant())
self.add_child(Extractor_Date())
self.add_child(Extractor_Photometrics())
self.add_child(Extractor_Path())
## EOF ##