168 lines
4.9 KiB
Python
168 lines
4.9 KiB
Python
""" PhotoCompare class to compare date/time/timezone in Photos to the exif data """
|
|
|
|
from collections import namedtuple
|
|
from typing import Callable, List, Optional, Tuple
|
|
|
|
from osxphotos import PhotosDB
|
|
from osxphotos.exiftool import ExifTool
|
|
from photoscript import Photo
|
|
|
|
from .datetime_utils import datetime_naive_to_local, datetime_to_new_tz
|
|
from .exif_datetime_updater import get_exif_date_time_offset
|
|
from .phototz import PhotoTimeZone
|
|
from .utils import noop
|
|
|
|
ExifDiff = namedtuple(
|
|
"ExifDiff",
|
|
[
|
|
"diff",
|
|
"photos_date",
|
|
"photos_time",
|
|
"photos_tz",
|
|
"exif_date",
|
|
"exif_time",
|
|
"exif_tz",
|
|
],
|
|
)
|
|
|
|
|
|
def change(msg: str) -> str:
|
|
"""Add change tag to string"""
|
|
return f"[change]{msg}[/change]"
|
|
|
|
|
|
def no_change(msg: str) -> str:
|
|
"""Add no change tag to string"""
|
|
return f"[no_change]{msg}[/no_change]"
|
|
|
|
|
|
class PhotoCompare:
|
|
"""Class to compare date/time/timezone in Photos to the exif data"""
|
|
|
|
def __init__(
|
|
self,
|
|
library_path: Optional[str] = None,
|
|
verbose: Optional[Callable] = None,
|
|
exiftool_path: Optional[str] = None,
|
|
):
|
|
self.library_path = library_path
|
|
self.db = PhotosDB(self.library_path)
|
|
self.verbose = verbose or noop
|
|
self.exiftool_path = exiftool_path
|
|
self.phototz = PhotoTimeZone(self.library_path)
|
|
|
|
def compare_exif(self, photo: Photo) -> List[str]:
|
|
"""Compare date/time/timezone in Photos to the exif data
|
|
|
|
Args:
|
|
photo (Photo): Photo object to compare
|
|
|
|
Returns:
|
|
List of strings:
|
|
"""
|
|
photos_offset_seconds, photos_tz_str, _ = self.phototz.get_timezone(photo)
|
|
photos_date = datetime_naive_to_local(photo.date)
|
|
photos_date = datetime_to_new_tz(photos_date, photos_offset_seconds)
|
|
photos_date_str = photos_date.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
photo_ = self.db.get_photo(photo.uuid)
|
|
if photo_path := photo_.path:
|
|
exif = ExifTool(filepath=photo_path, exiftool=self.exiftool_path)
|
|
exif_dict = exif.asdict()
|
|
exif_dt_offset = get_exif_date_time_offset(exif_dict)
|
|
exif_offset = exif_dt_offset.offset_str
|
|
exif_date = (
|
|
exif_dt_offset.datetime.strftime("%Y-%m-%d %H:%M:%S")
|
|
if exif_dt_offset.datetime
|
|
else ""
|
|
)
|
|
else:
|
|
exif_date = ""
|
|
exif_offset = ""
|
|
|
|
return [photos_date_str, photos_tz_str, exif_date, exif_offset]
|
|
|
|
def compare_exif_with_markup(self, photo: Photo) -> ExifDiff:
|
|
"""Compare date/time/timezone in Photos to the exif data and return an ExifDiff named tuple;
|
|
adds rich markup to strings to show differences
|
|
|
|
Args:
|
|
photo (Photo): Photo object to compare
|
|
"""
|
|
photos_date, photos_tz, exif_date, exif_tz = self.compare_exif(photo)
|
|
diff = False
|
|
photos_date, photos_time = photos_date.split(" ", 1)
|
|
try:
|
|
exif_date, exif_time = exif_date.split(" ", 1)
|
|
except ValueError:
|
|
exif_date = exif_date
|
|
exif_time = ""
|
|
|
|
if photos_date != exif_date:
|
|
photos_date = change(photos_date)
|
|
exif_date = change(exif_date)
|
|
diff = True
|
|
else:
|
|
photos_date = no_change(photos_date)
|
|
exif_date = no_change(exif_date)
|
|
|
|
if photos_time != exif_time:
|
|
photos_time = change(photos_time)
|
|
exif_time = change(exif_time)
|
|
diff = True
|
|
else:
|
|
photos_time = no_change(photos_time)
|
|
exif_time = no_change(exif_time)
|
|
|
|
if photos_tz != exif_tz:
|
|
photos_tz = change(photos_tz)
|
|
exif_tz = change(exif_tz)
|
|
diff = True
|
|
else:
|
|
photos_tz = no_change(photos_tz)
|
|
exif_tz = no_change(exif_tz)
|
|
|
|
return ExifDiff(
|
|
diff,
|
|
photos_date,
|
|
photos_time,
|
|
photos_tz,
|
|
exif_date,
|
|
exif_time,
|
|
exif_tz,
|
|
)
|
|
|
|
def compare_exif_no_markup(self, photo: Photo) -> ExifDiff:
|
|
"""Compare date/time/timezone in Photos to the exif data and return an ExifDiff named tuple;
|
|
|
|
Args:
|
|
photo (Photo): Photo object to compare
|
|
"""
|
|
photos_date, photos_tz, exif_date, exif_tz = self.compare_exif(photo)
|
|
diff = False
|
|
photos_date, photos_time = photos_date.split(" ", 1)
|
|
try:
|
|
exif_date, exif_time = exif_date.split(" ", 1)
|
|
except ValueError:
|
|
exif_date = exif_date
|
|
exif_time = ""
|
|
|
|
if photos_date != exif_date:
|
|
diff = True
|
|
|
|
if photos_time != exif_time:
|
|
diff = True
|
|
|
|
if photos_tz != exif_tz:
|
|
diff = True
|
|
|
|
return ExifDiff(
|
|
diff,
|
|
photos_date,
|
|
photos_time,
|
|
photos_tz,
|
|
exif_date,
|
|
exif_time,
|
|
exif_tz,
|
|
)
|