Update exif.py
This commit is contained in:
parent
d103b31bf2
commit
6da82cb755
1 changed files with 61 additions and 46 deletions
107
exif.py
107
exif.py
|
@ -1,5 +1,6 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
@ -7,7 +8,6 @@ from PIL import Image, IptcImagePlugin, TiffImagePlugin
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
""" Required exif values """
|
""" Required exif values """
|
||||||
TAGS_EXIF = {
|
TAGS_EXIF = {
|
||||||
0x9003: "DateTimeOriginal",
|
0x9003: "DateTimeOriginal",
|
||||||
|
@ -34,62 +34,81 @@ TAGS_IPTC = {
|
||||||
class Exif:
|
class Exif:
|
||||||
|
|
||||||
def __init__(self, filename: str):
|
def __init__(self, filename: str):
|
||||||
|
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self._exif = {}
|
self._exif = {}
|
||||||
self._iptc = {}
|
self._iptc = {}
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
""" Open image file """
|
self._process_image()
|
||||||
|
self._read_exif()
|
||||||
|
self._read_iptc()
|
||||||
|
|
||||||
|
def _process_image(self):
|
||||||
|
""" Open and verify image file """
|
||||||
try:
|
try:
|
||||||
with Image.open(self.filename) as self.img:
|
with Image.open(self.filename) as self.img:
|
||||||
self.img.verify()
|
self.img.verify()
|
||||||
except Exception:
|
except (OSError, IOError) as e:
|
||||||
logger.exception('Could not open image: %s', self.filename)
|
logger.exception('Could not open or verify image: %s', self.filename)
|
||||||
|
raise e
|
||||||
|
|
||||||
""" Read exif tags """
|
def _read_exif(self):
|
||||||
|
""" Read and process EXIF metadata """
|
||||||
try:
|
try:
|
||||||
self._exif = {
|
exif_data = self.img._getexif()
|
||||||
TAGS_EXIF[k]: v
|
if exif_data is not None:
|
||||||
for k, v in self.img._getexif().items()
|
self._exif = {
|
||||||
if k in TAGS_EXIF
|
TAGS_EXIF[k]: v
|
||||||
}
|
for k, v in exif_data.items()
|
||||||
for (key, value) in self._exif.items():
|
if k in TAGS_EXIF
|
||||||
if isinstance(value, TiffImagePlugin.IFDRational):
|
}
|
||||||
self.data[key] = float(value)
|
for key, value in self._exif.items():
|
||||||
elif isinstance(value, tuple):
|
if isinstance(value, TiffImagePlugin.IFDRational):
|
||||||
self.data[key] = tuple(float(t) if isinstance(t, TiffImagePlugin.IFDRational) else t for t in value)
|
self.data[key] = float(value)
|
||||||
elif isinstance(value, bytes):
|
elif isinstance(value, tuple):
|
||||||
self.data[key] = value.decode('utf-8')
|
self.data[key] = tuple(float(t) if isinstance(t, TiffImagePlugin.IFDRational) else t for t in value)
|
||||||
else:
|
elif isinstance(value, bytes):
|
||||||
self.data[key] = value
|
self.data[key] = value.decode('utf-8')
|
||||||
except Exception:
|
else:
|
||||||
logger.warning('Could not read exif metadata from: %s', self.filename)
|
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)
|
||||||
|
|
||||||
""" Read iptc tags """
|
def _read_iptc(self):
|
||||||
|
""" Read and process IPTC metadata """
|
||||||
try:
|
try:
|
||||||
self._iptc = {
|
iptc_data = IptcImagePlugin.getiptcinfo(self.img)
|
||||||
TAGS_IPTC[k]: v
|
if iptc_data is not None:
|
||||||
for k, v in IptcImagePlugin.getiptcinfo(self.img).items()
|
self._iptc = {
|
||||||
if k in TAGS_IPTC
|
TAGS_IPTC[k]: v
|
||||||
}
|
for k, v in iptc_data.items()
|
||||||
for (key, value) in self._iptc.items():
|
if k in TAGS_IPTC
|
||||||
if isinstance(value, list):
|
}
|
||||||
self.data[key] = [x.decode('utf-8') for x in value]
|
for key, value in self._iptc.items():
|
||||||
else:
|
if isinstance(value, list):
|
||||||
self.data[key] = value.decode('utf-8')
|
self.data[key] = [x.decode('utf-8') for x in value]
|
||||||
except Exception:
|
else:
|
||||||
logger.warning('Could not read iptc metadata from: %s', self.filename)
|
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)
|
||||||
|
|
||||||
def datetimeoriginal(self, timezone='UTC'):
|
def datetimeoriginal(self, timezone='UTC'):
|
||||||
""" Return DateTimeOriginal with timezone """
|
""" Return DateTimeOriginal with timezone, or fallback to file creation date if not available. """
|
||||||
if 'DateTimeOriginal' in self.data:
|
if 'DateTimeOriginal' in self.data:
|
||||||
try:
|
try:
|
||||||
return datetime.strptime(self.data['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S").astimezone(pytz.timezone(timezone))
|
return datetime.strptime(self.data['DateTimeOriginal'], "%Y:%m:%d %H:%M:%S").astimezone(pytz.timezone(timezone))
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
logger.warning('Could not parse date from: %s', self.filename)
|
logger.warning('Invalid EXIF DateTimeOriginal format in: %s', self.filename)
|
||||||
else:
|
|
||||||
return datetime.now(pytz.timezone(timezone))
|
# 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
|
||||||
|
|
||||||
def json(self):
|
def json(self):
|
||||||
""" Return exif data in json format """
|
""" Return exif data in json format """
|
||||||
|
@ -97,11 +116,7 @@ class Exif:
|
||||||
|
|
||||||
def keywords(self):
|
def keywords(self):
|
||||||
""" Return Keywords list """
|
""" Return Keywords list """
|
||||||
if 'Keywords' not in self.data:
|
return self.data.get('Keywords', [])
|
||||||
return []
|
|
||||||
if type(self.data['Keywords']) == list:
|
|
||||||
return self.data['Keywords']
|
|
||||||
return [self.data['Keywords']]
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.filename
|
return self.filename
|
Loading…
Reference in a new issue