Added ImportInfo for Photos 5+

This commit is contained in:
Rhet Turnbull
2020-08-16 22:57:33 -07:00
parent 360c8d8e1b
commit 98e417023e
9 changed files with 394 additions and 57 deletions

View File

@@ -15,6 +15,7 @@
+ [PhotoInfo](#photoinfo)
+ [ExifInfo](#exifinfo)
+ [AlbumInfo](#albuminfo)
+ [ImportInfo](#importinfo)
+ [FolderInfo](#folderinfo)
+ [PlaceInfo](#placeinfo)
+ [ScoreInfo](#scoreinfo)
@@ -767,6 +768,10 @@ Returns list of shared album names found in photos database (e.g. albums shared
**Note**: *Only valid for Photos 5 / MacOS 10.15*; on Photos <= 4, prints warning and returns empty list.
#### `import_info`
Returns a list of [ImportInfo](#importinfo) objects representing the import sessions for the database.
#### `folder_info`
```python
# assumes photosdb is a PhotosDB object (see above)
@@ -1053,6 +1058,9 @@ Returns a list of albums the photo is contained in. See also [album_info](#album
#### `album_info`
Returns a list of [AlbumInfo](#AlbumInfo) objects representing the albums the photo is contained in. See also [albums](#albums).
#### `import_info`
Returns an [ImportInfo](#importinfo) object representing the import session associated with the photo or `None` if there is no associated import session.
#### `persons`
Returns a list of the names of the persons in the photo
@@ -1378,6 +1386,15 @@ Returns the title or name of the album.
#### <a name="albumphotos">`photos`</a>
Returns a list of [PhotoInfo](#PhotoInfo) objects representing each photo contained in the album sorted in the same order as in Photos. (e.g. if photos were manually sorted in the Photos albums, photos returned by `photos` will be in same order as they appear in the Photos album)
#### `creation_date`
Returns the creation date as a timezone aware datetime.datetime object of the album.
#### `start_date`
Returns the date of earliest photo in the album as a timezone aware datetime.datetime object.
#### `end_date`
Returns the date of latest photo in the album as a timezone aware datetime.datetime object.
#### `folder_list`
Returns a hierarchical list of [FolderInfo](#FolderInfo) objects representing the folders the album is contained in. For example, if album "AlbumInFolder" is in SubFolder2 of Folder1 as illustrated below, would return a list of `FolderInfo` objects representing ["Folder1", "SubFolder2"]
@@ -1403,6 +1420,25 @@ Photos Library
#### `parent`
Returns a [FolderInfo](#FolderInfo) object representing the albums parent folder or `None` if album is not a in a folder.
### ImportInfo
PhotosDB.import_info returns a list of ImportInfo objects. Each ImportInfo object represents an import session in the library. PhotoInfo.import_info returns a single ImportInfo object representing the import session for the photo (or `None` if no associated import session).
**Note**: Photos 5+ only. Not implemented for Photos version <= 4.
#### `uuid`
Returns the universally unique identifier (uuid) of the import session. This is how Photos keeps track of individual objects within the database.
#### <a name="albumphotos">`photos`</a>
Returns a list of [PhotoInfo](#PhotoInfo) objects representing each photo contained in the album sorted in the same order as in Photos. (e.g. if photos were manually sorted in the Photos albums, photos returned by `photos` will be in same order as they appear in the Photos album)
#### `creation_date`
Returns the creation date as a timezone aware datetime.datetime object of the import session.
#### `start_date`
Returns the start date as a timezone aware datetime.datetime object for when the import session bega.
#### `end_date`
Returns the end date as a timezone aware datetime.datetime object for when the import session completed.
### FolderInfo
PhotosDB.folder_info returns a list of FolderInfo objects representing the top level folders in the library. Each FolderInfo object represents a single folder in the Photos library.

View File

@@ -3,6 +3,11 @@ Constants used by osxphotos
"""
import os.path
from datetime import datetime
# Time delta: add this to Photos times to get unix time
# Apple Epoch is Jan 1, 2001
TIME_DELTA = (datetime(2001, 1, 1, 0, 0) - datetime(1970, 1, 1, 0, 0)).total_seconds()
# which Photos library database versions have been tested
# Photos 2.0 (10.12.6) == 2622
@@ -36,11 +41,15 @@ _DB_TABLE_NAMES = {
"ASSET": "ZGENERICASSET",
"KEYWORD_JOIN": "Z_1KEYWORDS.Z_37KEYWORDS",
"ALBUM_JOIN": "Z_26ASSETS.Z_34ASSETS",
"ALBUM_SORT_ORDER": "Z_26ASSETS.Z_FOK_34ASSETS",
"IMPORT_FOK": "ZGENERICASSET.Z_FOK_IMPORTSESSION",
},
6: {
"ASSET": "ZASSET",
"KEYWORD_JOIN": "Z_1KEYWORDS.Z_36KEYWORDS",
"ALBUM_JOIN": "Z_26ASSETS.Z_3ASSETS",
"ALBUM_SORT_ORDER": "Z_26ASSETS.Z_FOK_3ASSETS",
"IMPORT_FOK": "null",
},
}
@@ -71,6 +80,7 @@ _PHOTOS_5_ALBUM_KIND = 2 # normal user album
_PHOTOS_5_SHARED_ALBUM_KIND = 1505 # shared album
_PHOTOS_5_FOLDER_KIND = 4000 # user folder
_PHOTOS_5_ROOT_FOLDER_KIND = 3999 # root folder
_PHOTOS_5_IMPORT_SESSION_ALBUM_KIND = 1506 # import session
_PHOTOS_4_ALBUM_KIND = 3 # RKAlbum.albumSubclass
_PHOTOS_4_TOP_LEVEL_ALBUM = "TopLevelAlbums"

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.32.0"
__version__ = "0.33.0"

View File

@@ -10,7 +10,7 @@ Represents a single Folder in the Photos library and provides access to the fold
PhotosDB.folders() returns a list of FolderInfo objects
"""
import logging
from datetime import datetime, timedelta, timezone
from ._constants import (
_PHOTOS_4_ALBUM_KIND,
@@ -18,11 +18,34 @@ from ._constants import (
_PHOTOS_4_VERSION,
_PHOTOS_5_ALBUM_KIND,
_PHOTOS_5_FOLDER_KIND,
TIME_DELTA,
)
from .datetime_utils import get_local_tz
class AlbumInfo:
def sort_list_by_keys(values, sort_keys):
""" Sorts list values by a second list sort_keys
e.g. given ["a","c","b"], [1, 3, 2], returns ["a", "b", "c"]
Args:
values: a list of values to be sorted
sort_keys: a list of keys to sort values by
Returns:
list of values, sorted by sort_keys
Raises:
ValueError: raised if len(values) != len(sort_keys)
"""
if len(values) != len(sort_keys):
return ValueError("values and sort_keys must have same length")
return list(zip(*sorted(zip(sort_keys, values))))[1]
class AlbumInfoBaseClass:
"""
Base class for AlbumInfo, ImportInfo
Info about a specific Album, contains all the details about the album
including folders, photos, etc.
"""
@@ -31,33 +54,107 @@ class AlbumInfo:
self._uuid = uuid
self._db = db
self._title = self._db._dbalbum_details[uuid]["title"]
@property
def title(self):
""" return title / name of album """
return self._title
self._creation_date_timestamp = self._db._dbalbum_details[uuid]["creation_date"]
self._start_date_timestamp = self._db._dbalbum_details[uuid]["start_date"]
self._end_date_timestamp = self._db._dbalbum_details[uuid]["end_date"]
self._local_tz = get_local_tz()
@property
def uuid(self):
""" return uuid of album """
return self._uuid
@property
def creation_date(self):
""" return creation date of album """
try:
return self._creation_date
except AttributeError:
try:
self._creation_date = (
datetime.fromtimestamp(
self._creation_date_timestamp + TIME_DELTA
).astimezone(tz=self._local_tz)
if self._creation_date_timestamp
else datetime(1970, 1, 1, 0, 0, 0).astimezone(
tz=timezone(timedelta(0))
)
)
except ValueError:
self._creation_date = datetime(1970, 1, 1, 0, 0, 0).astimezone(
tz=timezone(timedelta(0))
)
return self._creation_date
@property
def start_date(self):
""" For Albums, return start date (earliest image) of album or None for albums with no images
For Import Sessions, return start date of import session (when import began) """
try:
return self._start_date
except AttributeError:
try:
self._start_date = (
datetime.fromtimestamp(
self._start_date_timestamp + TIME_DELTA
).astimezone(tz=self._local_tz)
if self._start_date_timestamp
else None
)
except ValueError:
self._start_date = None
return self._start_date
@property
def end_date(self):
""" For Albums, return end date (most recent image) of album or None for albums with no images
For Import Sessions, return end date of import sessions (when import was completed) """
try:
return self._end_date
except AttributeError:
try:
self._end_date = (
datetime.fromtimestamp(
self._end_date_timestamp + TIME_DELTA
).astimezone(tz=self._local_tz)
if self._end_date_timestamp
else None
)
except ValueError:
self._end_date = None
return self._end_date
@property
def photos(self):
""" return list of photos contained in album """
return []
def __len__(self):
""" return number of photos contained in album """
return len(self.photos)
class AlbumInfo(AlbumInfoBaseClass):
"""
Base class for AlbumInfo, ImportInfo
Info about a specific Album, contains all the details about the album
including folders, photos, etc.
"""
@property
def title(self):
""" return title / name of album """
return self._title
@property
def photos(self):
""" return list of photos contained in album sorted in same sort order as Photos """
try:
return self._photos
except AttributeError:
if self.uuid in self._db._dbalbums_album:
uuid, sort_order = zip(*self._db._dbalbums_album[self.uuid])
self._photos = self._db.photos(uuid=uuid)
# PhotosDB.photos does not preserve order when passing in list of uuids
# so need to build photo list one a time
# sort uuids by sort order
sorted_uuid = sorted(zip(sort_order, uuid))
self._photos = [
self._db.photos(uuid=[uuid])[0] for _, uuid in sorted_uuid
]
sorted_uuid = sort_list_by_keys(uuid, sort_order)
self._photos = self._db.photos_by_uuid(sorted_uuid)
else:
self._photos = []
return self._photos
@@ -110,9 +207,24 @@ class AlbumInfo:
)
return self._parent
def __len__(self):
""" return number of photos contained in album """
return len(self.photos)
class ImportInfo(AlbumInfoBaseClass):
@property
def photos(self):
""" return list of photos contained in import session """
try:
return self._photos
except AttributeError:
uuid_list, sort_order = zip(
*[
(uuid, self._db._dbphotos[uuid]["fok_import_session"])
for uuid in self._db._dbphotos
if self._db._dbphotos[uuid]["import_uuid"] == self.uuid
]
)
sorted_uuid = sort_list_by_keys(uuid_list, sort_order)
self._photos = self._db.photos_by_uuid(sorted_uuid)
return self._photos
class FolderInfo:

View File

@@ -20,8 +20,7 @@ def datetime_remove_tz(dt):
if type(dt) != datetime.datetime:
raise TypeError(f"dt must be type datetime.datetime, not {type(dt)}")
dt_new = dt.replace(tzinfo=None)
return dt_new
return dt.replace(tzinfo=None)
def datetime_has_tz(dt):
@@ -32,9 +31,7 @@ def datetime_has_tz(dt):
if type(dt) != datetime.datetime:
raise TypeError(f"dt must be type datetime.datetime, not {type(dt)}")
if dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None:
return True
return False
return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
def datetime_naive_to_local(dt):
@@ -53,5 +50,4 @@ def datetime_naive_to_local(dt):
f"{dt} has tzinfo {dt.tzinfo} and offset {dt.tizinfo.utcoffset(dt)}"
)
dt_local = dt.replace(tzinfo=get_local_tz())
return dt_local
return dt.replace(tzinfo=get_local_tz())

View File

@@ -5,16 +5,12 @@ PhotosDB.photos() returns a list of PhotoInfo objects
"""
import dataclasses
import glob
import json
import logging
import os
import os.path
import pathlib
import subprocess
import sys
from datetime import timedelta, timezone
from pprint import pformat
import yaml
@@ -25,10 +21,11 @@ from .._constants import (
_PHOTOS_4_ROOT_FOLDER,
_PHOTOS_4_VERSION,
_PHOTOS_5_ALBUM_KIND,
_PHOTOS_5_IMPORT_SESSION_ALBUM_KIND,
_PHOTOS_5_SHARED_ALBUM_KIND,
_PHOTOS_5_SHARED_PHOTO_PATH,
)
from ..albuminfo import AlbumInfo
from ..albuminfo import AlbumInfo, ImportInfo
from ..personinfo import FaceInfo, PersonInfo
from ..phototemplate import PhotoTemplate
from ..placeinfo import PlaceInfo4, PlaceInfo5
@@ -88,7 +85,7 @@ class PhotoInfo:
def date(self):
""" image creation date as timezone aware datetime object """
return self._info["imageDate"]
@property
def date_modified(self):
""" image modification date as timezone aware datetime object
@@ -357,7 +354,7 @@ class PhotoInfo:
except AttributeError:
try:
faces = self._db._db_faceinfo_uuid[self._uuid]
self._faceinfo = [FaceInfo(db=self._db, pk=pk) for pk in faces]
self._faceinfo = [FaceInfo(db=self._db, pk=pk) for pk in faces]
except KeyError:
# no faces
self._faceinfo = []
@@ -387,6 +384,19 @@ class PhotoInfo:
]
return self._album_info
@property
def import_info(self):
""" ImportInfo object representing import session for the photo or None if no import session """
try:
return self._import_info
except AttributeError:
self._import_info = (
ImportInfo(db=self._db, uuid=self._info["import_uuid"])
if self._info["import_uuid"] is not None
else None
)
return self._import_info
@property
def keywords(self):
""" list of keywords for picture """
@@ -745,7 +755,7 @@ class PhotoInfo:
""" Return list of album UUIDs this photo is found in
Filters out albums in the trash and any special album types
Returns: list of album UUIDs
"""
if self._db._db_version <= _PHOTOS_4_VERSION:

View File

@@ -8,7 +8,6 @@ import os
import os.path
import pathlib
import platform
import sqlite3
import sys
import tempfile
from datetime import datetime, timedelta, timezone
@@ -26,15 +25,15 @@ from .._constants import (
_PHOTOS_4_VERSION,
_PHOTOS_5_ALBUM_KIND,
_PHOTOS_5_FOLDER_KIND,
_PHOTOS_5_IMPORT_SESSION_ALBUM_KIND,
_PHOTOS_5_ROOT_FOLDER_KIND,
_PHOTOS_5_SHARED_ALBUM_KIND,
_PHOTOS_5_VERSION,
_TESTED_DB_VERSIONS,
_TESTED_OS_VERSIONS,
_UNKNOWN_PERSON,
TIME_DELTA,
)
from .._version import __version__
from ..albuminfo import AlbumInfo, FolderInfo
from ..albuminfo import AlbumInfo, FolderInfo, ImportInfo
from ..datetime_utils import datetime_has_tz, datetime_naive_to_local
from ..personinfo import PersonInfo
from ..photoinfo import PhotoInfo
@@ -46,7 +45,7 @@ from ..utils import (
_open_sql_file,
get_last_library_path,
)
from .photosdb_utils import get_db_version, get_db_model_version
from .photosdb_utils import get_db_model_version, get_db_version
# TODO: Add test for imageTimeZoneOffsetSeconds = None
# TODO: Add test for __str__
@@ -485,6 +484,18 @@ class PhotosDB:
self._albums_shared = self._get_albums(shared=True)
return self._albums_shared
@property
def import_info(self):
""" return list of ImportInfo objects for each import session in the database """
try:
return self._import_info
except AttributeError:
self._import_info = [
ImportInfo(db=self, uuid=album)
for album in self._get_album_uuids(import_session=True)
]
return self._import_info
@property
def db_version(self):
""" return the database version as stored in LiGlobals table """
@@ -514,6 +525,7 @@ class PhotosDB:
""" If sqlite shared memory and write-ahead log files exist, those are copied too """
# required because python's sqlite3 implementation can't read a locked file
# _, suffix = os.path.splitext(fname)
dest_name = dest_path = ""
try:
dest_name = pathlib.Path(fname).name
dest_path = os.path.join(self._tempdir_name, dest_name)
@@ -536,9 +548,6 @@ class PhotosDB:
""" process the Photos database to extract info
works on Photos version <= 4.0 """
# Epoch is Jan 1, 2001
td = (datetime(2001, 1, 1, 0, 0) - datetime(1970, 1, 1, 0, 0)).total_seconds()
(conn, c) = _open_sql_file(self._tmp_db)
# get info to associate persons with photos
@@ -685,7 +694,8 @@ class PhotosDB:
isInTrash,
folderUuid,
albumType,
albumSubclass
albumSubclass,
createDate
FROM RKAlbum """
)
@@ -698,6 +708,7 @@ class PhotosDB:
# 5: folderUuid
# 6: albumType
# 7: albumSubclass -- if 3, normal user album
# 8: createDate
for album in c:
self._dbalbum_details[album[0]] = {
@@ -715,6 +726,9 @@ class PhotosDB:
"albumSubclass": album[7],
# for compatability with Photos 5 where album kind is ZKIND
"kind": album[7],
"creation_date": album[8],
"start_date": None, # Photos 5 only
"end_date": None, # Photos 5 only
}
# get details about folders
@@ -920,7 +934,7 @@ class PhotosDB:
# not accounted for
try:
self._dbphotos[uuid]["lastmodifieddate"] = datetime.fromtimestamp(
row[4] + td
row[4] + TIME_DELTA
)
except ValueError:
self._dbphotos[uuid]["lastmodifieddate"] = None
@@ -930,7 +944,7 @@ class PhotosDB:
self._dbphotos[uuid]["imageTimeZoneOffsetSeconds"] = row[9]
try:
imagedate = datetime.fromtimestamp(row[5] + td)
imagedate = datetime.fromtimestamp(row[5] + TIME_DELTA)
seconds = self._dbphotos[uuid]["imageTimeZoneOffsetSeconds"] or 0
delta = timedelta(seconds=seconds)
tz = timezone(delta)
@@ -1066,6 +1080,11 @@ class PhotosDB:
self._dbphotos[uuid]["original_orientation"] = row[38]
self._dbphotos[uuid]["original_filesize"] = row[39]
# import session not yet handled for Photos 4
self._dbphotos[uuid]["import_session"] = None
self._dbphotos[uuid]["import_uuid"] = None
self._dbphotos[uuid]["fok_import_session"] = None
# get additional details from RKMaster, needed for RAW processing
c.execute(
""" SELECT
@@ -1419,16 +1438,16 @@ class PhotosDB:
if _debug():
logging.debug(f"_process_database5")
# Epoch is Jan 1, 2001
td = (datetime(2001, 1, 1, 0, 0) - datetime(1970, 1, 1, 0, 0)).total_seconds()
(conn, c) = _open_sql_file(self._tmp_db)
# some of the tables/columns have different names in different versions of Photos
photos_ver = get_db_model_version(self._tmp_db)
self._photos_ver = photos_ver
asset_table = _DB_TABLE_NAMES[photos_ver]["ASSET"]
keyword_join = _DB_TABLE_NAMES[photos_ver]["KEYWORD_JOIN"]
album_join = _DB_TABLE_NAMES[photos_ver]["ALBUM_JOIN"]
(conn, c) = _open_sql_file(self._tmp_db)
album_sort = _DB_TABLE_NAMES[photos_ver]["ALBUM_SORT_ORDER"]
import_fok = _DB_TABLE_NAMES[photos_ver]["IMPORT_FOK"]
# Look for all combinations of persons and pictures
if _debug():
@@ -1539,7 +1558,7 @@ class PhotosDB:
f""" SELECT
ZGENERICALBUM.ZUUID,
{asset_table}.ZUUID,
{album_join}
{album_sort}
FROM {asset_table}
JOIN Z_26ASSETS ON {album_join} = {asset_table}.Z_PK
JOIN ZGENERICALBUM ON ZGENERICALBUM.Z_PK = Z_26ASSETS.Z_26ALBUMS
@@ -1577,7 +1596,10 @@ class PhotosDB:
"ZKIND, " # 6
"ZPARENTFOLDER, " # 7
"Z_PK, " # 8
"ZTRASHEDSTATE " # 9
"ZTRASHEDSTATE, " # 9
"ZCREATIONDATE, " # 10
"ZSTARTDATE, " # 11
"ZENDDATE " # 12
"FROM ZGENERICALBUM "
)
for album in c:
@@ -1594,6 +1616,9 @@ class PhotosDB:
"parentfolder": album[7],
"pk": album[8],
"intrash": False if album[9] == 0 else True,
"creation_date": album[10],
"start_date": album[11],
"end_date": album[12],
}
# add cross-reference by pk to uuid
@@ -1771,7 +1796,7 @@ class PhotosDB:
# I don't know what these mean but they will raise exception in datetime if
# not accounted for
try:
info["lastmodifieddate"] = datetime.fromtimestamp(row[4] + td)
info["lastmodifieddate"] = datetime.fromtimestamp(row[4] + TIME_DELTA)
except ValueError:
info["lastmodifieddate"] = None
except TypeError:
@@ -1780,7 +1805,7 @@ class PhotosDB:
info["imageTimeZoneOffsetSeconds"] = row[6]
try:
imagedate = datetime.fromtimestamp(row[5] + td)
imagedate = datetime.fromtimestamp(row[5] + TIME_DELTA)
seconds = info["imageTimeZoneOffsetSeconds"] or 0
delta = timedelta(seconds=seconds)
tz = timezone(delta)
@@ -1925,6 +1950,12 @@ class PhotosDB:
info["original_orientation"] = row[34]
info["original_filesize"] = row[35]
# initialize import session info which will be filled in later
# not every photo has an import session so initialize all records now
info["import_session"] = None
info["fok_import_session"] = None
info["import_uuid"] = None
# associated RAW image info
# will be filled in later
info["has_raw"] = False
@@ -1951,6 +1982,32 @@ class PhotosDB:
# else:
# info["burst"] = False
# get info on import sessions
# 0 ZGENERICASSET.ZUUID
# 1 ZGENERICASSET.ZIMPORTSESSION
# 2 ZGENERICASSET.Z_FOK_IMPORTSESSION
# 3 ZGENERICALBUM.ZUUID,
c.execute(
f"""SELECT
{asset_table}.ZUUID,
{asset_table}.ZIMPORTSESSION,
{import_fok},
ZGENERICALBUM.ZUUID
FROM
{asset_table}
JOIN ZGENERICALBUM ON ZGENERICALBUM.Z_PK = {asset_table}.ZIMPORTSESSION
"""
)
for row in c:
uuid = row[0]
try:
self._dbphotos[uuid]["import_session"] = row[1]
self._dbphotos[uuid]["fok_import_session"] = row[2]
self._dbphotos[uuid]["import_uuid"] = row[3]
except KeyError:
logging.debug(f"No info record for uuid {uuid} for import session")
# Get extended description
c.execute(
f"""SELECT {asset_table}.ZUUID,
@@ -2362,16 +2419,26 @@ class PhotosDB:
hierarchy = _recurse_folder_hierarchy(folders)
return hierarchy
def _get_album_uuids(self, shared=False):
def _get_album_uuids(self, shared=False, import_session=False):
""" Return list of album UUIDs found in photos database
Filters out albums in the trash and any special album types
Args:
shared: boolean; if True, returns shared albums, else normal albums
import_session: boolean, if True, returns import session albums, else normal or shared albums
Note: flags (shared, import_session) are mutually exclusive
Raises:
ValueError: raised if mutually exclusive flags passed
Returns: list of album UUIDs
"""
if shared and import_session:
raise ValueError(
"flags are mutually exclusive: pass zero or one of shared, import_session"
)
if self._db_version <= _PHOTOS_4_VERSION:
version4 = True
if shared:
@@ -2379,11 +2446,21 @@ class PhotosDB:
f"Shared albums not implemented for Photos library version {self._db_version}"
)
return [] # not implemented for _PHOTOS_4_VERSION
elif import_session:
logging.warning(
f"Import sessions not implemented for Photos library version {self._db_version}"
)
return [] # not implemented for _PHOTOS_4_VERSION
else:
album_kind = _PHOTOS_4_ALBUM_KIND
else:
version4 = False
album_kind = _PHOTOS_5_SHARED_ALBUM_KIND if shared else _PHOTOS_5_ALBUM_KIND
if shared:
album_kind = _PHOTOS_5_SHARED_ALBUM_KIND
elif import_session:
album_kind = _PHOTOS_5_IMPORT_SESSION_ALBUM_KIND
else:
album_kind = _PHOTOS_5_ALBUM_KIND
album_list = []
# look through _dbalbum_details because _dbalbums_album won't have empty albums it

View File

@@ -55,7 +55,10 @@ ALBUM_PHOTO_UUID_DICT = {
],
}
UUID_DICT = {"two_albums": "F12384F6-CD17-4151-ACBA-AE0E3688539E"}
UUID_DICT = {
"two_albums": "F12384F6-CD17-4151-ACBA-AE0E3688539E",
"album_dates": "0C514A98-7B77-4E4F-801B-364B7B65EAFA",
}
def test_folders_1():
@@ -228,6 +231,46 @@ def test_albums_photos():
assert photo.uuid in ALBUM_PHOTO_UUID_DICT[album.title]
def test_album_dates():
""" Test album date methods """
import datetime
import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
album = [a for a in photosdb.album_info if a.uuid == UUID_DICT["album_dates"]][0]
assert album.creation_date == datetime.datetime(
2019,
7,
27,
6,
19,
13,
706262,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200), "PDT"),
)
assert album.start_date == datetime.datetime(
2018,
9,
28,
12,
35,
49,
63000,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200), "PDT"),
)
assert album.end_date == datetime.datetime(
2018,
9,
28,
13,
9,
33,
22000,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200), "PDT"),
)
def test_photoinfo_albums():
""" Test PhotoInfo.albums """
import osxphotos

View File

@@ -74,6 +74,7 @@ UUID_DICT = {
"intrash": "71E3E212-00EB-430D-8A63-5E294B268554",
"not_intrash": "DC99FBDD-7A52-4100-A5BB-344131646C30",
"intrash_person_keywords": "6FD38366-3BF2-407D-81FE-7153EB6125B6",
"import_session": "8846E3E6-8AC8-4857-8448-E3D025784410",
}
UUID_PUMPKIN_FARM = [
@@ -1069,3 +1070,55 @@ def test_date_modified_invalid():
assert len(photos) == 1
p = photos[0]
assert p.date_modified is None
def test_import_session_count():
""" Test PhotosDB.import_session """
import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
import_sessions = photosdb.import_info
assert len(import_sessions) == 10
def test_import_session_photo():
""" Test photo.import_session """
import datetime
import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photo = photosdb.get_photo(UUID_DICT["import_session"])
import_session = photo.import_info
assert import_session.creation_date == datetime.datetime(
2020,
6,
6,
7,
15,
24,
729811,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200), "PDT"),
)
assert import_session.start_date == datetime.datetime(
2020,
6,
6,
7,
15,
24,
725564,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200), "PDT"),
)
assert import_session.end_date == datetime.datetime(
2020,
6,
6,
7,
15,
24,
725564,
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200), "PDT"),
)
assert len(import_session.photos) == 1