py-exif/exif.py

122 lines
4.2 KiB
Python
Raw Normal View History

2022-05-27 10:50:23 +03:00
import json
2023-02-02 20:37:45 +02:00
import logging
2024-11-20 15:07:15 +02:00
import os
2023-02-02 20:37:45 +02:00
from datetime import datetime
2022-05-27 10:50:23 +03:00
2023-02-02 20:37:45 +02:00
import pytz
2022-05-27 10:50:23 +03:00
from PIL import Image, IptcImagePlugin, TiffImagePlugin
2023-02-02 20:37:45 +02:00
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",
}
2022-05-27 10:50:23 +03:00
class Exif:
def __init__(self, filename: str):
self.filename = filename
2023-02-02 20:37:45 +02:00
self._exif = {}
self._iptc = {}
self.data = {}
2024-11-20 15:07:15 +02:00
self._process_image()
self._read_exif()
self._read_iptc()
def _process_image(self):
""" Open and verify image file """
2023-02-02 20:37:45 +02:00
try:
with Image.open(self.filename) as self.img:
self.img.verify()
2024-11-20 15:07:15 +02:00
except (OSError, IOError) as e:
logger.exception('Could not open or verify image: %s', self.filename)
raise e
2023-02-02 20:37:45 +02:00
2024-11-20 15:07:15 +02:00
def _read_exif(self):
""" Read and process EXIF metadata """
2022-05-27 10:50:23 +03:00
try:
2024-11-20 15:07:15 +02:00
exif_data = self.img._getexif()
if exif_data is not None:
self._exif = {
TAGS_EXIF[k]: v
for k, v in exif_data.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 AttributeError:
logger.warning('No EXIF metadata found for: %s', self.filename)
except Exception as e:
logger.warning('Could not read EXIF metadata from: %s, Error: %s', self.filename, e)
def _read_iptc(self):
""" Read and process IPTC metadata """
2023-02-02 20:37:45 +02:00
try:
2024-11-20 15:07:15 +02:00
iptc_data = IptcImagePlugin.getiptcinfo(self.img)
if iptc_data is not None:
self._iptc = {
TAGS_IPTC[k]: v
for k, v in iptc_data.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 as e:
logger.warning('Could not read IPTC metadata from: %s, Error: %s', self.filename, e)
2022-05-27 10:50:23 +03:00
2023-02-02 20:37:45 +02:00
def datetimeoriginal(self, timezone='UTC'):
2024-11-20 15:07:15 +02:00
""" Return DateTimeOriginal with timezone, or fallback to file creation date if not available. """
2023-02-02 20:37:45 +02:00
if 'DateTimeOriginal' in self.data:
try:
return datetime.strptime(self.data['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S").astimezone(pytz.timezone(timezone))
2024-11-20 15:07:15 +02:00
except ValueError:
logger.warning('Invalid EXIF DateTimeOriginal format in: %s', self.filename)
# If EXIF DateTimeOriginal is not available, fallback to file creation date
try:
creation_time = os.path.getctime(self.filename)
return datetime.fromtimestamp(creation_time).astimezone(pytz.timezone(timezone))
except Exception as e:
logger.warning('Could not retrieve file creation date for: %s, Error: %s', self.filename, e)
return datetime.now(pytz.timezone(timezone)) # Fallback to current time if all else fails
2022-05-27 10:50:23 +03:00
2023-02-02 20:37:45 +02:00
def json(self):
""" Return exif data in json format """
return json.dumps(self.data)
2022-05-27 10:50:23 +03:00
2023-02-02 20:37:45 +02:00
def keywords(self):
""" Return Keywords list """
2024-11-20 15:07:15 +02:00
return self.data.get('Keywords', [])
2022-05-27 10:50:23 +03:00
2023-02-02 20:37:45 +02:00
def __str__(self):
2024-11-20 15:07:15 +02:00
return self.filename