Skip to content

Commit

Permalink
Working on #1661
Browse files Browse the repository at this point in the history
  • Loading branch information
RhetTbull committed Jan 17, 2025
1 parent f840086 commit c4da6cf
Show file tree
Hide file tree
Showing 17 changed files with 3,031 additions and 2,807 deletions.
18 changes: 15 additions & 3 deletions osxphotos/cli/import_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1616,7 +1616,7 @@ def set_photo_metadata_from_exportdb(
f"Setting metadata and location from export database for [filename]{filepath.name}[/]"
)
set_photo_metadata_from_metadata(
photo, filepath, metadata, merge_keywords, True, verbose, dry_run
photo, filepath, metadata, merge_keywords, False, verbose, dry_run
)
else:
verbose(
Expand Down Expand Up @@ -1676,8 +1676,7 @@ def set_photo_metadata_from_metadata(
verbose(
f"Set date for [filename]{filepath.name}[/]: [time]{metadata.date.isoformat()}[/]"
)
if photo and not dry_run:
photo.date = metadata.date
set_photo_date(photo, metadata, verbose, dry_run)

return metadata

Expand Down Expand Up @@ -1706,6 +1705,19 @@ def set_photo_metadata_from_sidecar(
)


def set_photo_date(
photo: Photo | None,
metadata: MetaData,
verbose: Callable[..., None],
dry_run: bool,
) -> datetime.datetime:
"""Set photo date from metadata"""
print(f"Setting date for {photo=} to {metadata.tz_offset_sec=} {metadata.tzname=}")
if photo and not dry_run:
# ZZZ
photo.date = metadata.date


def set_photo_title(
photo: Photo | None,
filepath: pathlib.Path,
Expand Down
1 change: 1 addition & 0 deletions osxphotos/exif_datetime_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ def update_photos_from_exif(
dtinfo = self.get_date_time_offset_from_exif(
_photo.path, use_file_modify_date=use_file_modify_date
)
print(f"{dtinfo=}")
if dtinfo.used_file_modify_date:
self.verbose(
"EXIF date/time missing, using file modify date/time for "
Expand Down
4 changes: 2 additions & 2 deletions osxphotos/export_db_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,10 +610,10 @@ def export_db_get_photoinfo_for_filepath(
"""Return photoinfo object for a given filepath
Args:
exportdb: path to the export database
exportdir: path to the export directory or None
exportdb_path: path to the export database
filepath: absolute path to the file to retrieve info for from the database
exiftool: optional path to exiftool to be passed to the PhotoInfoFromDict object
exportdir_path: path to the export directory or None
Returns: PhotoInfoFromDict | None
"""
Expand Down
13 changes: 13 additions & 0 deletions osxphotos/iphoto.py
Original file line number Diff line number Diff line change
Expand Up @@ -1072,6 +1072,11 @@ def date(self) -> datetime.datetime:
default=True,
)

@property
def date_original(self) -> datetime.datetime:
"""Date photo was taken"""
return self.date

@property
def date_modified(self) -> datetime.datetime:
"""Date modified in library"""
Expand Down Expand Up @@ -1099,6 +1104,11 @@ def tzoffset(self) -> int:
tz = ZoneInfo(tzname)
return int(tz.utcoffset(self.date).total_seconds())

@property
def tzname(self) -> str | None:
"""Timezone name for the asset creation date"""
return self._db._db_photos[self._uuid]["timezone"] or None

@property
def path(self) -> str | None:
"""Path to original photo asset in library"""
Expand Down Expand Up @@ -1678,6 +1688,9 @@ def asdict(self, shallow: bool = True) -> dict[str, Any]:
dict_data["shared_moment"] = self.shared_moment
dict_data["shared_library"] = self.shared_library
dict_data["rating"] = self.rating
dict_data["screen_recording"] = self.screen_recording
dict_data["date_original"] = self.date_original
dict_data["tzname"] = self.tzname

return dict_data

Expand Down
3 changes: 3 additions & 0 deletions osxphotos/metadata_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class MetaData:
tz_offset_sec: int or None if not set, offset from UTC in seconds
height: int or None if not set, height of photo in pixels
width: int or None if not set, width of photo in pixels
tzname: str or None if not set, timezone name for timezone
"""

title: str = ""
Expand All @@ -57,6 +58,7 @@ class MetaData:
tz_offset_sec: float | None = None
height: int | None = None
width: int | None = None
tzname: str | None = None

def __ior__(self, other):
if isinstance(other, MetaData):
Expand Down Expand Up @@ -537,4 +539,5 @@ def metadata_from_photoinfo(photoinfo: PhotoInfoProtocol) -> MetaData:
tz_offset_sec=tz_offset,
height=photoinfo.height,
width=photoinfo.width,
tzname=photoinfo.tzname,
)
2 changes: 1 addition & 1 deletion osxphotos/photodates.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ def update_photo_time_for_new_timezone(
old_timezone_offset = PhotoTimeZone(library_path=library_path).get_timezone(photo)[
0
]
delta = old_timezone_offset - new_timezone.offset
photo_date = photo.date
delta = old_timezone_offset - new_timezone.offset_for_date(photo_date)
new_photo_date = update_datetime(
dt=photo_date, time_delta=datetime.timedelta(seconds=delta)
)
Expand Down
5 changes: 3 additions & 2 deletions osxphotos/photoexporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .export_db import ExportDBTemp
from .exportoptions import ExportOptions, ExportResults
from .fileutil import FileUtil
from .photoinfo_common import photoinfo_minify_dict
from .phototemplate import RenderOptions
from .platform import is_macos
from .rich_utils import add_rich_markup_tag
Expand Down Expand Up @@ -1045,7 +1046,7 @@ def _export_photo(
# set data in the database
with export_db.create_or_get_file_record(dest_str, self.photo.uuid) as rec:
if rec.photoinfo:
last_data = json.loads(rec.photoinfo)
last_data = photoinfo_minify_dict(json.loads(rec.photoinfo))
# to avoid issues with datetime comparisons, list order
# need to deserialize from photo.json() instead of using photo.asdict()
current_data = json.loads(self.photo.json(shallow=True))
Expand All @@ -1067,7 +1068,7 @@ def _json_default(o):
diff = json.dumps(diff, default=_json_default) if diff else None
else:
diff = None
rec.photoinfo = self.photo.json(shallow=True)
rec.photoinfo = self.photo.json(shallow=False)
rec.export_options = options.bit_flags
# don't set src_sig as that is set above before any modifications by convert_to_jpeg or exiftool
if not options.ignore_signature:
Expand Down
3 changes: 2 additions & 1 deletion osxphotos/photoinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def tzoffset(self) -> int:

@property
def tzname(self) -> str | None:
"""Timezone name for the Photos creation date; on Photos version < 5, returns None"""
"""Timezone name for the asset creation date; on Photos version < 5, returns None"""
return self._info["imageTimeZoneName"]

@property
Expand Down Expand Up @@ -2131,6 +2131,7 @@ def asdict(self, shallow: bool = True) -> dict[str, Any]:
dict_data["rating"] = self.rating
dict_data["screen_recording"] = self.screen_recording
dict_data["date_original"] = self.date_original
dict_data["tzname"] = self.tzname

return dict_data

Expand Down
38 changes: 38 additions & 0 deletions osxphotos/photoinfo_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
""" Common utilities for PhotoInfo variations """

from typing import Any

# These are PhotoInfo.asdict() keys that that are removed from the output
# by the PhotoExporter for comparing PhotoInfo objects.

FULL_KEYS = [
"album_info",
"path_derivatives",
"adjustments",
"burst_album_info",
"burst_albums",
"burst_default_pick",
"burst_key",
"burst_photos",
"burst_selected",
"cloud_metadata",
"import_info",
"labels_normalized",
"person_info",
"project_info",
"search_info",
"search_info_normalized",
"syndicated",
"saved_to_library",
"shared_moment",
"shared_library",
"rating",
"screen_recording",
"date_original",
"tzname",
]


def photoinfo_minify_dict(info: dict[str, Any]) -> dict[str, Any]:
"""Convert a full PhotoInfo dict to a minimum PhotoInfo dict"""
return {k: v for k, v in info.items() if k not in FULL_KEYS}
21 changes: 15 additions & 6 deletions osxphotos/photoinfo_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,23 @@ class PhotoInfoFromDict(PhotoInfoMixin):
@property
def album_info(self) -> AlbumInfoFromDict:
"""Return AlbumInfo objects for photo"""
if getattr(self, "_album_info"):
return self._album_info
# this is a little hacky but it works for `osxphotos import` use case
if not getattr(self, "folders"):
return []
# self.folders is a rehydrated object so need access it's __dict__ to get the actual data
return [
AlbumInfoFromDict(title, folders)
for title, folders in self.folders.__dict__.items()
]
self._album_info = []
else:
# self.folders is a rehydrated object so need access it's __dict__ to get the actual data
self._album_info = [
AlbumInfoFromDict(title, folders)
for title, folders in self.folders.__dict__.items()
]
return self._album_info

@album_info.setter
def album_info(self, value):
"""If rehydrating class has album_info, then set it"""
self._album_info = value

def asdict(self) -> dict[str, Any]:
"""Return the PhotoInfo dictionary"""
Expand Down
4 changes: 4 additions & 0 deletions osxphotos/photoinfo_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def date_modified(self) -> datetime.datetime | None: ...
@property
def tzoffset(self) -> int: ...

@property
def tzname(self) -> str: ...

@property
def path(self) -> str | None: ...

Expand Down Expand Up @@ -346,6 +349,7 @@ def __getattr__(self, name):
"fingerprint",
"syndicated",
"shared_moment_info",
"tzname",
]:
return None
elif name in [
Expand Down
6 changes: 6 additions & 0 deletions osxphotos/timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ def offset_str(self) -> str:
def abbreviation(self) -> str:
return self.timezone.abbreviation()

def offset_for_date(self, dt: datetime.datetime) -> int:
return self.timezone.secondsFromGMTForDate_(dt)

def offset_str_for_date(self, dt: datetime.datetime) -> str:
return format_offset_time(self.offset_for_date(dt))

def tzinfo(self, dt: datetime.datetime) -> zoneinfo.ZoneInfo:
"""Return zoneinfo.ZoneInfo object for the timezone at the given datetime"""
if not self._from_offset:
Expand Down
4 changes: 3 additions & 1 deletion tests/config_timewarp_ventura.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,9 @@ def is_dst() -> bool:
f"{TEST_LIBRARY_TIMEWARP}/originals/7/7E9DF2EE-A5B0-4077-80EC-30565221A3B9.jpeg"
),
"",
get_local_utc_offset_str("2023-08-21 09:18:13"),
get_local_utc_offset_str(get_file_timestamp(
f"{TEST_LIBRARY_TIMEWARP}/originals/7/7E9DF2EE-A5B0-4077-80EC-30565221A3B9.jpeg"
)),
"",
),
},
Expand Down
Loading

0 comments on commit c4da6cf

Please sign in to comment.