Refactored album code in photosdb to fix issue #169

This commit is contained in:
Rhet Turnbull
2020-06-20 17:31:33 -07:00
parent a23259948c
commit cfabd0dbea
6 changed files with 123 additions and 85 deletions

View File

@@ -1,3 +1,3 @@
""" version info """ """ version info """
__version__ = "0.29.23" __version__ = "0.29.24"

View File

@@ -348,16 +348,15 @@ class PhotoInfo:
return self._albums return self._albums
except AttributeError: except AttributeError:
self._albums = [] self._albums = []
if self._db._db_version <= _PHOTOS_4_VERSION: album_kinds = (
album_kinds = [_PHOTOS_4_ALBUM_KIND] [_PHOTOS_4_ALBUM_KIND]
kind_field = "albumSubclass" if self._db._db_version <= _PHOTOS_4_VERSION
else: else [_PHOTOS_5_ALBUM_KIND, _PHOTOS_5_SHARED_ALBUM_KIND]
album_kinds = [_PHOTOS_5_ALBUM_KIND, _PHOTOS_5_SHARED_ALBUM_KIND] )
kind_field = "kind"
for album in self._info["albums"]: for album in self._info["albums"]:
detail = self._db._dbalbum_details[album] detail = self._db._dbalbum_details[album]
if detail[kind_field] in album_kinds and not detail["intrash"]: if detail["kind"] in album_kinds and not detail["intrash"]:
self._albums.append(detail["title"]) self._albums.append(detail["title"])
return self._albums return self._albums
@@ -368,16 +367,15 @@ class PhotoInfo:
return self._album_info return self._album_info
except AttributeError: except AttributeError:
self._album_info = [] self._album_info = []
if self._db._db_version <= _PHOTOS_4_VERSION: album_kinds = (
album_kinds = [_PHOTOS_4_ALBUM_KIND] [_PHOTOS_4_ALBUM_KIND]
kind_field = "albumSubclass" if self._db._db_version <= _PHOTOS_4_VERSION
else: else [_PHOTOS_5_ALBUM_KIND, _PHOTOS_5_SHARED_ALBUM_KIND]
album_kinds = [_PHOTOS_5_ALBUM_KIND, _PHOTOS_5_SHARED_ALBUM_KIND] )
kind_field = "kind"
for album in self._info["albums"]: for album in self._info["albums"]:
detail = self._db._dbalbum_details[album] detail = self._db._dbalbum_details[album]
if detail[kind_field] in album_kinds and not detail["intrash"]: if detail["kind"] in album_kinds and not detail["intrash"]:
self._album_info.append(AlbumInfo(db=self._db, uuid=album)) self._album_info.append(AlbumInfo(db=self._db, uuid=album))
return self._album_info return self._album_info

View File

@@ -311,18 +311,16 @@ class PhotosDB:
def albums_as_dict(self): def albums_as_dict(self):
""" return albums as dict of albums, count in reverse sorted order (descending) """ """ return albums as dict of albums, count in reverse sorted order (descending) """
albums = {} albums = {}
album_keys = [ album_keys = self._get_album_uuids(shared=False)
k for album in album_keys:
for k in self._dbalbums_album.keys() title = self._dbalbum_details[album]["title"]
if self._dbalbum_details[k]["cloudownerhashedpersonid"] is None if album in self._dbalbums_album:
and not self._dbalbum_details[k]["intrash"] try:
] albums[title] += len(self._dbalbums_album[album])
for k in album_keys: except KeyError:
title = self._dbalbum_details[k]["title"] albums[title] = len(self._dbalbums_album[album])
if title in albums:
albums[title] += len(self._dbalbums_album[k])
else: else:
albums[title] = len(self._dbalbums_album[k]) albums[title] = 0 # empty album
albums = dict(sorted(albums.items(), key=lambda kv: kv[1], reverse=True)) albums = dict(sorted(albums.items(), key=lambda kv: kv[1], reverse=True))
return albums return albums
@@ -331,25 +329,17 @@ class PhotosDB:
""" returns shared albums as dict of albums, count in reverse sorted order (descending) """ returns shared albums as dict of albums, count in reverse sorted order (descending)
valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict """ valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict """
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
if self._db_version <= _PHOTOS_4_VERSION:
logging.warning(
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
)
return {}
albums = {} albums = {}
album_keys = [ album_keys = self._get_album_uuids(shared=True)
k for album in album_keys:
for k in self._dbalbums_album.keys() title = self._dbalbum_details[album]["title"]
if self._dbalbum_details[k]["cloudownerhashedpersonid"] is not None if album in self._dbalbums_album:
] try:
for k in album_keys: albums[title] += len(self._dbalbums_album[album])
title = self._dbalbum_details[k]["title"] except KeyError:
if title in albums: albums[title] = len(self._dbalbums_album[album])
albums[title] += len(self._dbalbums_album[k])
else: else:
albums[title] = len(self._dbalbums_album[k]) albums[title] = 0 # empty album
albums = dict(sorted(albums.items(), key=lambda kv: kv[1], reverse=True)) albums = dict(sorted(albums.items(), key=lambda kv: kv[1], reverse=True))
return albums return albums
@@ -410,32 +400,28 @@ class PhotosDB:
@property @property
def album_info(self): def album_info(self):
""" return list of AlbumInfo objects for each album in the photos database """ """ return list of AlbumInfo objects for each album in the photos database """
try:
return [ return self._album_info
AlbumInfo(db=self, uuid=album) except AttributeError:
for album in self._dbalbums_album.keys() self._album_info = [
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is None AlbumInfo(db=self, uuid=album)
and not self._dbalbum_details[album]["intrash"] for album in self._get_album_uuids(shared=False)
] ]
return self._album_info
@property @property
def album_info_shared(self): def album_info_shared(self):
""" return list of AlbumInfo objects for each shared album in the photos database """ return list of AlbumInfo objects for each shared album in the photos database
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list """ only valid for Photos 5; on Photos <= 4, prints warning and returns empty list """
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album # if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
try:
if self._db_version <= _PHOTOS_4_VERSION: return self._album_info_shared
logging.warning( except AttributeError:
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}" self._album_info_shared = [
) AlbumInfo(db=self, uuid=album)
return [] for album in self._get_album_uuids(shared=True)
]
return [ return self._album_info_shared
AlbumInfo(db=self, uuid=album)
for album in self._dbalbums_album.keys()
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is not None
and not self._dbalbum_details[album]["intrash"]
]
@property @property
def albums(self): def albums(self):
@@ -444,13 +430,11 @@ class PhotosDB:
# Could be more than one album with same name # Could be more than one album with same name
# Right now, they are treated as same album and photos are combined from albums with same name # Right now, they are treated as same album and photos are combined from albums with same name
albums = { try:
self._dbalbum_details[album]["title"] return self._albums
for album in self._dbalbums_album.keys() except AttributeError:
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is None self._albums = self._get_albums(shared=False)
and not self._dbalbum_details[album]["intrash"] return self._albums
}
return list(albums)
@property @property
def albums_shared(self): def albums_shared(self):
@@ -462,19 +446,11 @@ class PhotosDB:
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album # if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
if self._db_version <= _PHOTOS_4_VERSION: try:
logging.warning( return self._albums_shared
f"album_names_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}" except AttributeError:
) self._albums_shared = self._get_albums(shared=True)
return [] return self._albums_shared
albums = {
self._dbalbum_details[album]["title"]
for album in self._dbalbums_album.keys()
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is not None
and not self._dbalbum_details[album]["intrash"]
}
return list(albums)
@property @property
def db_version(self): def db_version(self):
@@ -634,6 +610,8 @@ class PhotosDB:
"folderUuid": album[5], "folderUuid": album[5],
"albumType": album[6], "albumType": album[6],
"albumSubclass": album[7], "albumSubclass": album[7],
# for compatability with Photos 5 where album kind is ZKIND
"kind": album[7],
} }
# get details about folders # get details about folders
@@ -2098,6 +2076,65 @@ class PhotosDB:
hierarchy = _recurse_folder_hierarchy(folders) hierarchy = _recurse_folder_hierarchy(folders)
return hierarchy return hierarchy
def _get_album_uuids(self, shared=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
Returns: list of album names
"""
if self._db_version <= _PHOTOS_4_VERSION:
version4 = True
if shared:
logging.warning(
f"Shared albums 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
album_list = []
# look through _dbalbum_details because _dbalbums_album won't have empty albums it
for album, detail in self._dbalbum_details.items():
if (
detail["kind"] == album_kind
and not detail["intrash"]
and (
(shared and detail["cloudownerhashedpersonid"] is not None)
or (not shared and detail["cloudownerhashedpersonid"] is None)
)
and (
# in Photos 4, special albums like "printAlbum" have kind _PHOTOS_4_ALBUM_KIND
# but should not be listed here; they can be distinguished by looking
# for folderUuid of _PHOTOS_4_ROOT_FOLDER as opposed to _PHOTOS_4_TOP_LEVEL_ALBUM
(version4 and detail["folderUuid"] != _PHOTOS_4_ROOT_FOLDER)
or not version4
)
):
album_list.append(album)
return album_list
def _get_albums(self, shared=False):
""" Return list of album titles found in photos database
Albums may have duplicate titles -- these will be treated as a single album.
Filters out albums in the trash and any special album types
Args:
shared: boolean; if True, returns shared albums, else normal albums
Returns: list of album names
"""
album_uuids = self._get_album_uuids(shared=shared)
return list({self._dbalbum_details[album]["title"] for album in album_uuids})
def photos( def photos(
self, self,
keywords=None, keywords=None,

View File

@@ -16,7 +16,7 @@ KEYWORDS = [
"United Kingdom", "United Kingdom",
] ]
PERSONS = ["Katie", "Suzy", "Maria"] PERSONS = ["Katie", "Suzy", "Maria"]
ALBUMS = ["Pumpkin Farm", "Last Import", "AlbumInFolder"] ALBUMS = ["Pumpkin Farm", "AlbumInFolder"]
KEYWORDS_DICT = { KEYWORDS_DICT = {
"Kids": 4, "Kids": 4,
"wedding": 2, "wedding": 2,
@@ -29,7 +29,7 @@ KEYWORDS_DICT = {
"United Kingdom": 1, "United Kingdom": 1,
} }
PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1} PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1}
ALBUM_DICT = {"Pumpkin Farm": 3, "Last Import": 1, "AlbumInFolder": 1} ALBUM_DICT = {"Pumpkin Farm": 3, "AlbumInFolder": 1}
def test_init(): def test_init():

View File

@@ -28,6 +28,7 @@ ALBUMS = [
"AlbumInFolder", "AlbumInFolder",
"Raw", "Raw",
"I have a deleted twin", # there's an empty album with same name that has been deleted "I have a deleted twin", # there's an empty album with same name that has been deleted
"EmptyAlbum",
] ]
KEYWORDS_DICT = { KEYWORDS_DICT = {
"Kids": 4, "Kids": 4,
@@ -47,6 +48,7 @@ ALBUM_DICT = {
"AlbumInFolder": 2, "AlbumInFolder": 2,
"Raw": 4, "Raw": 4,
"I have a deleted twin": 1, "I have a deleted twin": 1,
"EmptyAlbum": 0,
} # Note: there are 2 albums named "Test Album" for testing duplicate album names } # Note: there are 2 albums named "Test Album" for testing duplicate album names
UUID_DICT = { UUID_DICT = {

View File

@@ -261,6 +261,7 @@ ALBUMS_JSON = {
"AlbumInFolder": 2, "AlbumInFolder": 2,
"Test Album": 2, "Test Album": 2,
"I have a deleted twin": 1, "I have a deleted twin": 1,
"EmptyAlbum": 0,
}, },
"shared albums": {}, "shared albums": {},
} }