import json import logging from datetime import datetime import pytz from PIL import Image, IptcImagePlugin, TiffImagePlugin logger = logging.getLogger(__name__) """ Required exif values """ TAGS_EXIF = { 0x9003: "DateTimeOriginal", 0x829D: "FNumber", 0x829A: "ExposureTime", 0x8827: "ISOSpeedRatings", 0x9204: "ExposureBiasValue", 0x8822: "ExposureProgram", 0x9207: "MeteringMode", 0x920A: "FocalLength", 0xA405: "FocalLengthIn35mmFilm", 0x0110: "Model", 0xA434: "LensModel", } """ Required iptc values """ TAGS_IPTC = { (2, 25): "Keywords", (2, 55): "DateCreated", (2, 60): "TimeCreated", } class Exif: def __init__(self, filename: str): self.filename = filename self._exif = {} self._iptc = {} self.data = {} """ Open image file """ try: with Image.open(self.filename) as self.img: self.img.verify() except Exception: logger.exception('Could not open image: %s', self.filename) """ Read exif tags """ try: self._exif = { TAGS_EXIF[k]: v for k, v in self.img._getexif().items() if k in TAGS_EXIF } for (key, value) in self._exif.items(): if isinstance(value, TiffImagePlugin.IFDRational): self.data[key] = float(value) elif isinstance(value, tuple): self.data[key] = tuple(float(t) if isinstance(t, TiffImagePlugin.IFDRational) else t for t in value) elif isinstance(value, bytes): self.data[key] = value.decode('utf-8') else: self.data[key] = value except Exception: logger.warning('Could not read exif metadata from: %s', self.filename) """ Read iptc tags """ try: self._iptc = { TAGS_IPTC[k]: v for k, v in IptcImagePlugin.getiptcinfo(self.img).items() if k in TAGS_IPTC } for (key, value) in self._iptc.items(): if isinstance(value, list): self.data[key] = [x.decode('utf-8') for x in value] else: self.data[key] = value.decode('utf-8') except Exception: logger.warning('Could not read iptc metadata from: %s', self.filename) def datetimeoriginal(self, timezone='UTC'): """ Return DateTimeOriginal with timezone """ if 'DateTimeOriginal' in self.data: try: return datetime.strptime(self.data['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S").astimezone(pytz.timezone(timezone)) except ValueError as e: logger.warning('Could not parse date from: %s', self.filename) else: return datetime.now(pytz.timezone(timezone)) def json(self): """ Return exif data in json format """ return json.dumps(self.data) def keywords(self): """ Return Keywords list """ if 'Keywords' not in self.data: return [] if type(self.data['Keywords']) == list: return self.data['Keywords'] return [self.data['Keywords']] def __str__(self): return self.filename