Added date_modified to PhotoInfo
This commit is contained in:
@@ -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)
|
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`
|
#### `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`
|
#### `description`
|
||||||
Returns the description of the photo
|
Returns the description of the photo
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ def get_photos_db(*db_options):
|
|||||||
click.echo(f"Using Photos library: {db}", err=True)
|
click.echo(f"Using Photos library: {db}", err=True)
|
||||||
return db
|
return db
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Click CLI object & context settings
|
# Click CLI object & context settings
|
||||||
@@ -863,9 +863,11 @@ def print_photo_info(photos, json=False):
|
|||||||
"path_live_photo",
|
"path_live_photo",
|
||||||
"iscloudasset",
|
"iscloudasset",
|
||||||
"incloud",
|
"incloud",
|
||||||
|
"date_modified",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
for p in photos:
|
for p in photos:
|
||||||
|
date_modified_iso = p.date_modified.isoformat() if p.date_modified else None
|
||||||
dump.append(
|
dump.append(
|
||||||
[
|
[
|
||||||
p.uuid,
|
p.uuid,
|
||||||
@@ -895,6 +897,7 @@ def print_photo_info(photos, json=False):
|
|||||||
p.path_live_photo,
|
p.path_live_photo,
|
||||||
p.iscloudasset,
|
p.iscloudasset,
|
||||||
p.incloud,
|
p.incloud,
|
||||||
|
date_modified_iso,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
for row in dump:
|
for row in dump:
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.22.5"
|
__version__ = "0.22.6"
|
||||||
|
|||||||
@@ -64,6 +64,20 @@ class PhotoInfo:
|
|||||||
imagedate_utc = imagedate.astimezone(tz=tz)
|
imagedate_utc = imagedate.astimezone(tz=tz)
|
||||||
return imagedate_utc
|
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
|
@property
|
||||||
def tzoffset(self):
|
def tzoffset(self):
|
||||||
""" timezone offset from UTC in seconds """
|
""" timezone offset from UTC in seconds """
|
||||||
@@ -630,6 +644,9 @@ class PhotoInfo:
|
|||||||
exif["DateTimeOriginal"] = datetimeoriginal
|
exif["DateTimeOriginal"] = datetimeoriginal
|
||||||
exif["OffsetTimeOriginal"] = offsettime
|
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])
|
json_str = json.dumps([exif])
|
||||||
return json_str
|
return json_str
|
||||||
|
|
||||||
@@ -660,11 +677,16 @@ class PhotoInfo:
|
|||||||
return f"osxphotos.{self.__class__.__name__}(db={self._db}, uuid='{self._uuid}', info={self._info})"
|
return f"osxphotos.{self.__class__.__name__}(db={self._db}, uuid='{self._uuid}', info={self._info})"
|
||||||
|
|
||||||
def __str__(self):
|
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 = {
|
info = {
|
||||||
"uuid": self.uuid,
|
"uuid": self.uuid,
|
||||||
"filename": self.filename,
|
"filename": self.filename,
|
||||||
"original_filename": self.original_filename,
|
"original_filename": self.original_filename,
|
||||||
"date": str(self.date),
|
"date": date_iso,
|
||||||
"description": self.description,
|
"description": self.description,
|
||||||
"title": self.title,
|
"title": self.title,
|
||||||
"keywords": self.keywords,
|
"keywords": self.keywords,
|
||||||
@@ -688,11 +710,15 @@ class PhotoInfo:
|
|||||||
"path_live_photo": self.path_live_photo,
|
"path_live_photo": self.path_live_photo,
|
||||||
"iscloudasset": self.iscloudasset,
|
"iscloudasset": self.iscloudasset,
|
||||||
"incloud": self.incloud,
|
"incloud": self.incloud,
|
||||||
|
"date_modified": date_modified_iso,
|
||||||
}
|
}
|
||||||
return yaml.dump(info, sort_keys=False)
|
return yaml.dump(info, sort_keys=False)
|
||||||
|
|
||||||
def json(self):
|
def json(self):
|
||||||
""" return JSON representation """
|
""" return JSON representation """
|
||||||
|
|
||||||
|
date_modified_iso = self.date_modified.isoformat() if self.date_modified else None
|
||||||
|
|
||||||
pic = {
|
pic = {
|
||||||
"uuid": self.uuid,
|
"uuid": self.uuid,
|
||||||
"filename": self.filename,
|
"filename": self.filename,
|
||||||
@@ -721,6 +747,7 @@ class PhotoInfo:
|
|||||||
"path_live_photo": self.path_live_photo,
|
"path_live_photo": self.path_live_photo,
|
||||||
"iscloudasset": self.iscloudasset,
|
"iscloudasset": self.iscloudasset,
|
||||||
"incloud": self.incloud,
|
"incloud": self.incloud,
|
||||||
|
"date_modified": date_modified_iso,
|
||||||
}
|
}
|
||||||
return json.dumps(pic)
|
return json.dumps(pic)
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class PhotosDB:
|
|||||||
if dbfile:
|
if dbfile:
|
||||||
# shouldn't pass via both *args and dbfile=
|
# shouldn't pass via both *args and dbfile=
|
||||||
raise TypeError(
|
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_,
|
||||||
dbfile,
|
dbfile,
|
||||||
)
|
)
|
||||||
@@ -532,19 +532,10 @@ class PhotosDB:
|
|||||||
self._dbphotos[uuid]["modelID"] = row[1]
|
self._dbphotos[uuid]["modelID"] = row[1]
|
||||||
self._dbphotos[uuid]["masterUuid"] = row[2]
|
self._dbphotos[uuid]["masterUuid"] = row[2]
|
||||||
self._dbphotos[uuid]["filename"] = row[3]
|
self._dbphotos[uuid]["filename"] = row[3]
|
||||||
|
self._dbphotos[uuid]["lastmodifieddate"] = (
|
||||||
try:
|
datetime.fromtimestamp(row[4] + td) if row[4] is not None else None
|
||||||
self._dbphotos[uuid]["lastmodifieddate"] = datetime.fromtimestamp(
|
)
|
||||||
row[4] + td
|
self._dbphotos[uuid]["imageDate"] = datetime.fromtimestamp(row[5] + 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]["mainRating"] = row[6]
|
self._dbphotos[uuid]["mainRating"] = row[6]
|
||||||
self._dbphotos[uuid]["hasAdjustments"] = row[7]
|
self._dbphotos[uuid]["hasAdjustments"] = row[7]
|
||||||
self._dbphotos[uuid]["hasKeywords"] = row[8]
|
self._dbphotos[uuid]["hasKeywords"] = row[8]
|
||||||
@@ -647,7 +638,6 @@ class PhotosDB:
|
|||||||
JOIN RKModelResource on RKModelResource.attachedModelId = RKVersion.modelId
|
JOIN RKModelResource on RKModelResource.attachedModelId = RKVersion.modelId
|
||||||
WHERE RKVersion.isInTrash = 0 """
|
WHERE RKVersion.isInTrash = 0 """
|
||||||
)
|
)
|
||||||
# get info on path of live photo movie
|
|
||||||
|
|
||||||
# Order of results:
|
# Order of results:
|
||||||
# 0 RKVersion.uuid
|
# 0 RKVersion.uuid
|
||||||
@@ -1008,11 +998,9 @@ class PhotosDB:
|
|||||||
info["masterUuid"] = None
|
info["masterUuid"] = None
|
||||||
info["masterFingerprint"] = row[1]
|
info["masterFingerprint"] = row[1]
|
||||||
info["name"] = row[2]
|
info["name"] = row[2]
|
||||||
try:
|
info["lastmodifieddate"] = (
|
||||||
info["lastmodifieddate"] = datetime.fromtimestamp(row[4] + td)
|
datetime.fromtimestamp(row[4] + td) if row[4] is not None else None
|
||||||
except:
|
)
|
||||||
info["lastmodifieddate"] = datetime.fromtimestamp(row[5] + td)
|
|
||||||
|
|
||||||
info["imageDate"] = datetime.fromtimestamp(row[5] + td)
|
info["imageDate"] = datetime.fromtimestamp(row[5] + td)
|
||||||
info["imageTimeZoneOffsetSeconds"] = row[6]
|
info["imageTimeZoneOffsetSeconds"] = row[6]
|
||||||
info["hidden"] = row[9]
|
info["hidden"] = row[9]
|
||||||
|
|||||||
@@ -455,8 +455,9 @@ def test_exiftool_json_sidecar():
|
|||||||
"GPSLongitude": "0 deg 7\' 54.50\\" W",
|
"GPSLongitude": "0 deg 7\' 54.50\\" W",
|
||||||
"GPSPosition": "51 deg 30\' 12.86\\" N, 0 deg 7\' 54.50\\" W",
|
"GPSPosition": "51 deg 30\' 12.86\\" N, 0 deg 7\' 54.50\\" W",
|
||||||
"GPSLatitudeRef": "North", "GPSLongitudeRef": "West",
|
"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()
|
json_got = photos[0]._exiftool_json_sidecar()
|
||||||
|
|||||||
@@ -397,7 +397,9 @@ def test_exiftool_json_sidecar():
|
|||||||
"GPSLongitude": "0 deg 7\' 54.50\\" W",
|
"GPSLongitude": "0 deg 7\' 54.50\\" W",
|
||||||
"GPSPosition": "51 deg 30\' 12.86\\" N, 0 deg 7\' 54.50\\" W",
|
"GPSPosition": "51 deg 30\' 12.86\\" N, 0 deg 7\' 54.50\\" W",
|
||||||
"GPSLatitudeRef": "North", "GPSLongitudeRef": "West",
|
"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"}]
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
29
tests/test_modified_date_catalina_10_15_1.py
Normal file
29
tests/test_modified_date_catalina_10_15_1.py
Normal file
@@ -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
|
||||||
|
|
||||||
30
tests/test_modified_date_mojave_10_14_6.py
Normal file
30
tests/test_modified_date_mojave_10_14_6.py
Normal file
@@ -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
|
||||||
|
|
||||||
Reference in New Issue
Block a user