From 67b0ae0bf679815372d415c3064e21d46a5b8718 Mon Sep 17 00:00:00 2001 From: Rhet Turnbull Date: Sat, 25 Jan 2020 08:07:50 -0800 Subject: [PATCH] Added date_modified to PhotoInfo --- README.md | 5 +++- osxphotos/__main__.py | 5 +++- osxphotos/_version.py | 2 +- osxphotos/photoinfo.py | 29 ++++++++++++++++++- osxphotos/photosdb.py | 28 ++++++------------ tests/test_export_catalina_10_15_1.py | 5 ++-- tests/test_export_mojave_10_14_6.py | 4 ++- tests/test_modified_date_catalina_10_15_1.py | 29 +++++++++++++++++++ tests/test_modified_date_mojave_10_14_6.py | 30 ++++++++++++++++++++ 9 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 tests/test_modified_date_catalina_10_15_1.py create mode 100644 tests/test_modified_date_mojave_10_14_6.py diff --git a/README.md b/README.md index c47f430b..d8125177 100644 --- a/README.md +++ b/README.md @@ -544,7 +544,10 @@ Returns the current filename of the photo on disk. See also [original_filename] Returns the original filename of the photo when it was imported to Photos. **Note**: Photos 5.0+ renames the photo when it adds the file to the library using UUID. See also [filename](#filename) #### `date` -Returns the date of the photo as a datetime.datetime object +Returns the create date of the photo as a datetime.datetime object + +#### `date_modified` +Returns the modification date of the photo as a datetime.datetime object or None if photo has no modification date #### `description` Returns the description of the photo diff --git a/osxphotos/__main__.py b/osxphotos/__main__.py index 1d1d2ad4..7bdff566 100644 --- a/osxphotos/__main__.py +++ b/osxphotos/__main__.py @@ -46,7 +46,7 @@ def get_photos_db(*db_options): click.echo(f"Using Photos library: {db}", err=True) return db else: - return None + return None # Click CLI object & context settings @@ -863,9 +863,11 @@ def print_photo_info(photos, json=False): "path_live_photo", "iscloudasset", "incloud", + "date_modified", ] ) for p in photos: + date_modified_iso = p.date_modified.isoformat() if p.date_modified else None dump.append( [ p.uuid, @@ -895,6 +897,7 @@ def print_photo_info(photos, json=False): p.path_live_photo, p.iscloudasset, p.incloud, + date_modified_iso, ] ) for row in dump: diff --git a/osxphotos/_version.py b/osxphotos/_version.py index ed3ae623..f229a01e 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.22.5" +__version__ = "0.22.6" diff --git a/osxphotos/photoinfo.py b/osxphotos/photoinfo.py index 07b2f04c..b0060b37 100644 --- a/osxphotos/photoinfo.py +++ b/osxphotos/photoinfo.py @@ -64,6 +64,20 @@ class PhotoInfo: imagedate_utc = imagedate.astimezone(tz=tz) return imagedate_utc + @property + def date_modified(self): + """ image modification date as timezone aware datetime object + or None if no modification date set """ + imagedate = self._info["lastmodifieddate"] + if imagedate: + seconds = self._info["imageTimeZoneOffsetSeconds"] or 0 + delta = timedelta(seconds=seconds) + tz = timezone(delta) + imagedate_utc = imagedate.astimezone(tz=tz) + return imagedate_utc + else: + return None + @property def tzoffset(self): """ timezone offset from UTC in seconds """ @@ -630,6 +644,9 @@ class PhotoInfo: exif["DateTimeOriginal"] = datetimeoriginal exif["OffsetTimeOriginal"] = offsettime + if self.date_modified is not None: + exif["ModifyDate"] = self.date_modified.strftime("%Y:%m:%d %H:%M:%S") + json_str = json.dumps([exif]) return json_str @@ -660,11 +677,16 @@ class PhotoInfo: return f"osxphotos.{self.__class__.__name__}(db={self._db}, uuid='{self._uuid}', info={self._info})" def __str__(self): + """ string representation of PhotoInfo object """ + + date_iso = self.date.isoformat() + date_modified_iso = self.date_modified.isoformat() if self.date_modified else None + info = { "uuid": self.uuid, "filename": self.filename, "original_filename": self.original_filename, - "date": str(self.date), + "date": date_iso, "description": self.description, "title": self.title, "keywords": self.keywords, @@ -688,11 +710,15 @@ class PhotoInfo: "path_live_photo": self.path_live_photo, "iscloudasset": self.iscloudasset, "incloud": self.incloud, + "date_modified": date_modified_iso, } return yaml.dump(info, sort_keys=False) def json(self): """ return JSON representation """ + + date_modified_iso = self.date_modified.isoformat() if self.date_modified else None + pic = { "uuid": self.uuid, "filename": self.filename, @@ -721,6 +747,7 @@ class PhotoInfo: "path_live_photo": self.path_live_photo, "iscloudasset": self.iscloudasset, "incloud": self.incloud, + "date_modified": date_modified_iso, } return json.dumps(pic) diff --git a/osxphotos/photosdb.py b/osxphotos/photosdb.py index 6f73fa12..197e7fa2 100644 --- a/osxphotos/photosdb.py +++ b/osxphotos/photosdb.py @@ -94,7 +94,7 @@ class PhotosDB: if dbfile: # shouldn't pass via both *args and dbfile= raise TypeError( - f"photos database path must be specified as argument or named parameter dbfile but not both: args: {args}, dbfile: {dbfile}", + f"photos database path must be specified as argument or named parameter dbfile but not both: args: {dbfile_}, dbfile: {dbfile}", dbfile_, dbfile, ) @@ -532,19 +532,10 @@ class PhotosDB: self._dbphotos[uuid]["modelID"] = row[1] self._dbphotos[uuid]["masterUuid"] = row[2] self._dbphotos[uuid]["filename"] = row[3] - - try: - self._dbphotos[uuid]["lastmodifieddate"] = datetime.fromtimestamp( - row[4] + td - ) - except: - self._dbphotos[uuid]["lastmodifieddate"] = datetime.fromtimestamp( - row[5] + td - ) - - self._dbphotos[uuid]["imageDate"] = datetime.fromtimestamp( - row[5] + td - ) # - row[9], timezone.utc) + self._dbphotos[uuid]["lastmodifieddate"] = ( + datetime.fromtimestamp(row[4] + td) if row[4] is not None else None + ) + self._dbphotos[uuid]["imageDate"] = datetime.fromtimestamp(row[5] + td) self._dbphotos[uuid]["mainRating"] = row[6] self._dbphotos[uuid]["hasAdjustments"] = row[7] self._dbphotos[uuid]["hasKeywords"] = row[8] @@ -647,7 +638,6 @@ class PhotosDB: JOIN RKModelResource on RKModelResource.attachedModelId = RKVersion.modelId WHERE RKVersion.isInTrash = 0 """ ) - # get info on path of live photo movie # Order of results: # 0 RKVersion.uuid @@ -1008,11 +998,9 @@ class PhotosDB: info["masterUuid"] = None info["masterFingerprint"] = row[1] info["name"] = row[2] - try: - info["lastmodifieddate"] = datetime.fromtimestamp(row[4] + td) - except: - info["lastmodifieddate"] = datetime.fromtimestamp(row[5] + td) - + info["lastmodifieddate"] = ( + datetime.fromtimestamp(row[4] + td) if row[4] is not None else None + ) info["imageDate"] = datetime.fromtimestamp(row[5] + td) info["imageTimeZoneOffsetSeconds"] = row[6] info["hidden"] = row[9] diff --git a/tests/test_export_catalina_10_15_1.py b/tests/test_export_catalina_10_15_1.py index 696cc36e..988d9a3d 100644 --- a/tests/test_export_catalina_10_15_1.py +++ b/tests/test_export_catalina_10_15_1.py @@ -455,8 +455,9 @@ def test_exiftool_json_sidecar(): "GPSLongitude": "0 deg 7\' 54.50\\" W", "GPSPosition": "51 deg 30\' 12.86\\" N, 0 deg 7\' 54.50\\" W", "GPSLatitudeRef": "North", "GPSLongitudeRef": "West", - "DateTimeOriginal": "2018:10:13 09:18:12", "OffsetTimeOriginal": "-04:00"}] - """ + "DateTimeOriginal": "2018:10:13 09:18:12", + "OffsetTimeOriginal": "-04:00", + "ModifyDate": "2019:12:08 14:06:44"}] """ ) json_got = photos[0]._exiftool_json_sidecar() diff --git a/tests/test_export_mojave_10_14_6.py b/tests/test_export_mojave_10_14_6.py index a542e917..9f41b657 100644 --- a/tests/test_export_mojave_10_14_6.py +++ b/tests/test_export_mojave_10_14_6.py @@ -397,7 +397,9 @@ def test_exiftool_json_sidecar(): "GPSLongitude": "0 deg 7\' 54.50\\" W", "GPSPosition": "51 deg 30\' 12.86\\" N, 0 deg 7\' 54.50\\" W", "GPSLatitudeRef": "North", "GPSLongitudeRef": "West", - "DateTimeOriginal": "2018:10:13 09:18:12", "OffsetTimeOriginal": "-04:00"}] + "DateTimeOriginal": "2018:10:13 09:18:12", + "OffsetTimeOriginal": "-04:00", + "ModifyDate": "2019:12:01 11:43:45"}] """ ) diff --git a/tests/test_modified_date_catalina_10_15_1.py b/tests/test_modified_date_catalina_10_15_1.py new file mode 100644 index 00000000..a4556284 --- /dev/null +++ b/tests/test_modified_date_catalina_10_15_1.py @@ -0,0 +1,29 @@ +import pytest + +PHOTOS_DB = "./tests/Test-Shared-10.15.1.photoslibrary/database/photos.db" +PHOTOS_DB_PATH = "/Test-Shared-10.15.1.photoslibrary/database/photos.db" +PHOTOS_LIBRARY_PATH = "/Test-Shared-10.15.1.photoslibrary" + +UUID_DICT = { + "modified": "37210110-E940-4227-92D3-45C40F68EB0A", + "not_modified": "35243F7D-88C4-4408-B516-C74406E90C15", +} + + +def test_modified(): + import datetime + import osxphotos + + photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) + photos = photosdb.photos(uuid=[UUID_DICT["modified"]]) + assert photos[0].date_modified is not None + assert photos[0].date_modified.isoformat() == "2019-12-26T21:08:48.306538-07:00" + + +def test_not_modified(): + import osxphotos + + photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) + photos = photosdb.photos(uuid=[UUID_DICT["not_modified"]]) + assert photos[0].date_modified is None + diff --git a/tests/test_modified_date_mojave_10_14_6.py b/tests/test_modified_date_mojave_10_14_6.py new file mode 100644 index 00000000..9d4b0c0a --- /dev/null +++ b/tests/test_modified_date_mojave_10_14_6.py @@ -0,0 +1,30 @@ +import pytest + +PHOTOS_DB = "./tests/Test-10.14.6.photoslibrary/database/photos.db" +PHOTOS_DB_PATH = "/Test-10.14.6.photoslibrary/database/photos.db" +PHOTOS_LIBRARY_PATH = "/Test-Shared-10.14.6.photoslibrary" + +UUID_DICT = { + "modified": "3Jn73XpSQQCluzRBMWRsMA", + "not_modified": "35243F7D-88C4-4408-B516-C74406E90C15", +} + + +def test_modified(): + import datetime + import osxphotos + + photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) + photos = photosdb.photos(uuid=[UUID_DICT["modified"]]) + assert photos[0].date_modified is not None + assert photos[0].date_modified.isoformat() == "2019-12-01T11:43:45.714123-04:00" + + +# no non-modified photos in the 10.14.6 database +# def test_not_modified(): +# import osxphotos + +# photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) +# photos = photosdb.photos(uuid=[UUID_DICT["not_modified"]]) +# assert photos[0].date_modified is None +