Source code for tagit.model.extractor

"""

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 ##