Changed PhotosDB albums interface as prep for adding folders

This commit is contained in:
Rhet Turnbull
2020-04-10 17:30:37 -07:00
parent 626e460aab
commit 3e5062684a
13 changed files with 78 additions and 56 deletions

View File

@@ -368,7 +368,7 @@ def main():
photosdb = osxphotos.PhotosDB(db) photosdb = osxphotos.PhotosDB(db)
print(photosdb.keywords) print(photosdb.keywords)
print(photosdb.persons) print(photosdb.persons)
print(photosdb.albums) print(photosdb.album_names)
print(photosdb.keywords_as_dict) print(photosdb.keywords_as_dict)
print(photosdb.persons_as_dict) print(photosdb.persons_as_dict)
@@ -554,17 +554,17 @@ keywords = photosdb.keywords
Returns a list of the keywords found in the Photos library Returns a list of the keywords found in the Photos library
#### `albums` #### `album_names`
```python ```python
# assumes photosdb is a PhotosDB object (see above) # assumes photosdb is a PhotosDB object (see above)
albums = photosdb.albums albums = photosdb.album_names
``` ```
Returns a list of the albums found in the Photos library. Returns a list of the albums found in the Photos library.
**Note**: In Photos 5.0 (MacOS 10.15/Catalina), It is possible to have more than one album with the same name in Photos. Albums with duplicate names are treated as a single album and the photos in each are combined. For example, if you have two albums named "Wedding" and each has 2 photos, osxphotos will treat this as a single album named "Wedding" with 4 photos in it. **Note**: In Photos 5.0 (MacOS 10.15/Catalina), It is possible to have more than one album with the same name in Photos. Albums with duplicate names are treated as a single album and the photos in each are combined. For example, if you have two albums named "Wedding" and each has 2 photos, osxphotos will treat this as a single album named "Wedding" with 4 photos in it.
#### `albums_shared` #### `album_names_shared`
Returns list of shared albums found in photos database (e.g. albums shared via iCloud photo sharing) Returns list of shared albums found in photos database (e.g. albums shared via iCloud photo sharing)
@@ -1113,7 +1113,7 @@ def main():
print(photosdb.keywords) print(photosdb.keywords)
print(photosdb.persons) print(photosdb.persons)
print(photosdb.albums) print(photosdb.album_names)
print(photosdb.keywords_as_dict) print(photosdb.keywords_as_dict)
print(photosdb.persons_as_dict) print(photosdb.persons_as_dict)

View File

@@ -14,7 +14,7 @@ def main():
print(photosdb.keywords) print(photosdb.keywords)
print(photosdb.persons) print(photosdb.persons)
print(photosdb.albums) print(photosdb.album_names)
print(photosdb.keywords_as_dict) print(photosdb.keywords_as_dict)
print(photosdb.persons_as_dict) print(photosdb.persons_as_dict)

View File

@@ -1,3 +1,3 @@
""" version info """ """ version info """
__version__ = "0.25.1" __version__ = "0.26.0"

View File

@@ -120,6 +120,12 @@ class PhotosDB:
# e.g. {'1EB2B765-0765-43BA-A90C-0D0580E6172C': ['0C514A98-7B77-4E4F-801B-364B7B65EAFA']} # e.g. {'1EB2B765-0765-43BA-A90C-0D0580E6172C': ['0C514A98-7B77-4E4F-801B-364B7B65EAFA']}
self._dbalbums_uuid = {} self._dbalbums_uuid = {}
# Dict with information about all albums/photos by primary key in the album database
# key is album pk, value is album uuid
# e.g. {'43': '0C514A98-7B77-4E4F-801B-364B7B65EAFA'}
# specific to Photos versions >= 5
self._dbalbums_pk = {}
# Dict with information about all albums/photos by album # Dict with information about all albums/photos by album
# key is album UUID, value is list of photo UUIDs contained in that album # key is album UUID, value is list of photo UUIDs contained in that album
# e.g. {'0C514A98-7B77-4E4F-801B-364B7B65EAFA': ['1EB2B765-0765-43BA-A90C-0D0580E6172C']} # e.g. {'0C514A98-7B77-4E4F-801B-364B7B65EAFA': ['1EB2B765-0765-43BA-A90C-0D0580E6172C']}
@@ -264,6 +270,7 @@ class PhotosDB:
k k
for k in self._dbalbums_album.keys() for k in self._dbalbums_album.keys()
if self._dbalbum_details[k]["cloudownerhashedpersonid"] is None if self._dbalbum_details[k]["cloudownerhashedpersonid"] is None
and self._dbalbum_details[k]["intrash"] == 0
] ]
for k in album_keys: for k in album_keys:
title = self._dbalbum_details[k]["title"] title = self._dbalbum_details[k]["title"]
@@ -314,7 +321,7 @@ class PhotosDB:
return list(persons) return list(persons)
@property @property
def albums(self): def album_names(self):
""" return list of albums found in photos database """ """ return list of albums found in photos database """
# Could be more than one album with same name # Could be more than one album with same name
@@ -331,7 +338,7 @@ class PhotosDB:
return list(albums) return list(albums)
@property @property
def albums_shared(self): def album_names_shared(self):
""" return list of shared albums found in photos database """ return list of shared albums found in 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 """
@@ -466,9 +473,9 @@ class PhotosDB:
"uuid, " # 0 "uuid, " # 0
"name, " # 1 "name, " # 1
"cloudLibraryState, " # 2 "cloudLibraryState, " # 2
"cloudIdentifier " # 3 "cloudIdentifier, " # 3
"isInTrash " # 4
"FROM RKAlbum " "FROM RKAlbum "
"WHERE isInTrash = 0"
) )
for album in c: for album in c:
@@ -476,6 +483,7 @@ class PhotosDB:
"title": album[1], "title": album[1],
"cloudlibrarystate": album[2], "cloudlibrarystate": album[2],
"cloudidentifier": album[3], "cloudidentifier": album[3],
"intrash": album[4],
"cloudlocalstate": None, # Photos 5 "cloudlocalstate": None, # Photos 5
"cloudownerfirstname": None, # Photos 5 "cloudownerfirstname": None, # Photos 5
"cloudownderlastname": None, # Photos 5 "cloudownderlastname": None, # Photos 5
@@ -848,10 +856,8 @@ class PhotosDB:
) )
place_ids = [id[0] for id in place_ids_query.fetchall()] place_ids = [id[0] for id in place_ids_query.fetchall()]
self._dbphotos[uuid]["placeIDs"] = place_ids self._dbphotos[uuid]["placeIDs"] = place_ids
country_code = [ country_code = [countries[x] for x in place_ids if x in countries]
countries[x] for x in place_ids if x in countries
]
if len(country_code) > 1: if len(country_code) > 1:
logging.warning(f"Found more than one country code for uuid: {uuid}") logging.warning(f"Found more than one country code for uuid: {uuid}")
@@ -986,6 +992,7 @@ class PhotosDB:
logging.debug(pformat(self._dbfaces_person)) logging.debug(pformat(self._dbfaces_person))
logging.debug(self._dbfaces_uuid) logging.debug(self._dbfaces_uuid)
# get details about albums
c.execute( c.execute(
"SELECT ZGENERICALBUM.ZUUID, ZGENERICASSET.ZUUID " "SELECT ZGENERICALBUM.ZUUID, ZGENERICASSET.ZUUID "
"FROM ZGENERICASSET " "FROM ZGENERICASSET "
@@ -995,12 +1002,15 @@ class PhotosDB:
) )
for album in c: for album in c:
# store by uuid in _dbalbums_uuid and by album in _dbalbums_album # store by uuid in _dbalbums_uuid and by album in _dbalbums_album
if not album[1] in self._dbalbums_uuid: try:
self._dbalbums_uuid[album[1]] = [] self._dbalbums_uuid[album[1]].append(album[0])
if not album[0] in self._dbalbums_album: except KeyError:
self._dbalbums_album[album[0]] = [] self._dbalbums_uuid[album[1]] = [album[0]]
self._dbalbums_uuid[album[1]].append(album[0])
self._dbalbums_album[album[0]].append(album[1]) try:
self._dbalbums_album[album[0]].append(album[1])
except KeyError:
self._dbalbums_album[album[0]] = [album[1]]
# now get additional details about albums # now get additional details about albums
c.execute( c.execute(
@@ -1010,8 +1020,12 @@ class PhotosDB:
"ZCLOUDLOCALSTATE, " # 2 "ZCLOUDLOCALSTATE, " # 2
"ZCLOUDOWNERFIRSTNAME, " # 3 "ZCLOUDOWNERFIRSTNAME, " # 3
"ZCLOUDOWNERLASTNAME, " # 4 "ZCLOUDOWNERLASTNAME, " # 4
"ZCLOUDOWNERHASHEDPERSONID " # 5 "ZCLOUDOWNERHASHEDPERSONID, " # 5
"FROM ZGENERICALBUM" "ZKIND, " # 6
"ZPARENTFOLDER, " # 7
"Z_PK, " # 8
"ZTRASHEDSTATE " # 9
"FROM ZGENERICALBUM "
) )
for album in c: for album in c:
self._dbalbum_details[album[0]] = { self._dbalbum_details[album[0]] = {
@@ -1021,9 +1035,18 @@ class PhotosDB:
"cloudownderlastname": album[4], "cloudownderlastname": album[4],
"cloudownerhashedpersonid": album[5], "cloudownerhashedpersonid": album[5],
"cloudlibrarystate": None, # Photos 4 "cloudlibrarystate": None, # Photos 4
"cloudidentifier": None, # Photos4 "cloudidentifier": None, # Photos 4
"kind": album[6],
"parentfolder": album[7],
"pk": album[8],
"intrash": album[9],
} }
# add cross-reference by pk to uuid
# needed to extract folder hierarchy
# in Photos >= 5, folders are special albums
self._dbalbums_pk[album[8]] = album[0]
if _debug(): if _debug():
logging.debug(f"Finished walking through albums") logging.debug(f"Finished walking through albums")
logging.debug(pformat(self._dbalbums_album)) logging.debug(pformat(self._dbalbums_album))

View File

@@ -72,13 +72,13 @@ def test_keywords():
assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords)
def test_albums(): def test_album_names():
import osxphotos import osxphotos
import collections import collections
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
assert "Pumpkin Farm" in photosdb.albums assert "Pumpkin Farm" in photosdb.album_names
assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names)
def test_keywords_dict(): def test_keywords_dict():

View File

@@ -150,13 +150,13 @@ def test_keywords():
assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords)
def test_albums(): def test_album_names():
import osxphotos import osxphotos
import collections import collections
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
assert "Pumpkin Farm" in photosdb.albums assert "Pumpkin Farm" in photosdb.album_names
assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names)
def test_keywords_dict(): def test_keywords_dict():

View File

@@ -150,13 +150,13 @@ def test_keywords():
assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords)
def test_albums(): def test_album_names():
import osxphotos import osxphotos
import collections import collections
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
assert "Pumpkin Farm" in photosdb.albums assert "Pumpkin Farm" in photosdb.album_names
assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names)
def test_keywords_dict(): def test_keywords_dict():

View File

@@ -52,12 +52,12 @@ def test_keywords():
assert photosdb.keywords == [] assert photosdb.keywords == []
def test_albums(): def test_album_names():
import osxphotos import osxphotos
import collections import collections
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
assert photosdb.albums == [] assert photosdb.album_names == []
def test_keywords_dict(): def test_keywords_dict():

View File

@@ -71,13 +71,13 @@ def test_keywords():
assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords)
def test_albums(): def test_album_names():
import osxphotos import osxphotos
import collections import collections
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
assert "Pumpkin Farm" in photosdb.albums assert "Pumpkin Farm" in photosdb.album_names
assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names)
def test_keywords_dict(): def test_keywords_dict():

View File

@@ -71,13 +71,13 @@ def test_keywords():
assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords)
def test_albums(): def test_album_names():
import osxphotos import osxphotos
import collections import collections
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
assert "Pumpkin Farm" in photosdb.albums assert "Pumpkin Farm" in photosdb.album_names
assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names)
def test_keywords_dict(): def test_keywords_dict():

View File

@@ -76,13 +76,13 @@ def test_keywords():
assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords)
def test_albums(): def test_album_names():
import osxphotos import osxphotos
import collections import collections
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
assert "Pumpkin Farm" in photosdb.albums assert "Pumpkin Farm" in photosdb.album_names
assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names)
def test_keywords_dict(): def test_keywords_dict():

View File

@@ -26,11 +26,11 @@ UUID_SHARED = [
UUID_NOT_SHARED = ["37210110-E940-4227-92D3-45C40F68EB0A"] UUID_NOT_SHARED = ["37210110-E940-4227-92D3-45C40F68EB0A"]
def test_albums(): def test_album_names():
import osxphotos import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
albums = photosdb.albums albums = photosdb.album_names
assert len(albums) == 1 assert len(albums) == 1
assert albums[0] == ALBUMS[0] assert albums[0] == ALBUMS[0]
@@ -40,7 +40,7 @@ def test_albums_shared():
import osxphotos import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
albums_shared = photosdb.albums_shared albums_shared = photosdb.album_names_shared
assert len(albums_shared) == 1 assert len(albums_shared) == 1
assert albums_shared[0] == ALBUMS_SHARED[0] assert albums_shared[0] == ALBUMS_SHARED[0]

View File

@@ -11,22 +11,22 @@ ALBUMS = ["Pumpkin Farm", "Test Album", "Test Album (1)"]
ALBUM_DICT = {"Pumpkin Farm": 3, "Test Album": 1, "Test Album (1)": 1} ALBUM_DICT = {"Pumpkin Farm": 3, "Test Album": 1, "Test Album (1)": 1}
def test_albums(): def test_album_names():
import osxphotos import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
albums = photosdb.albums albums = photosdb.album_names
assert len(albums) == len(ALBUMS) assert len(albums) == len(ALBUMS)
for album in albums: for album in albums:
assert album in ALBUMS assert album in ALBUMS
def test_albums_shared(): def test_albums_names_shared():
import osxphotos import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
albums_shared = photosdb.albums_shared albums_shared = photosdb.album_names_shared
assert len(albums_shared) == 0 assert len(albums_shared) == 0
@@ -65,4 +65,3 @@ def test_not_shared():
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photos = [p for p in photosdb.photos() if not p.shared] photos = [p for p in photosdb.photos() if not p.shared]
assert len(photos) == 7 assert len(photos) == 7