diff --git a/README.md b/README.md index 24d55733..6c24c54d 100644 --- a/README.md +++ b/README.md @@ -368,7 +368,7 @@ def main(): photosdb = osxphotos.PhotosDB(db) print(photosdb.keywords) print(photosdb.persons) - print(photosdb.albums) + print(photosdb.album_names) print(photosdb.keywords_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 -#### `albums` +#### `album_names` ```python # 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. **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) @@ -1113,7 +1113,7 @@ def main(): print(photosdb.keywords) print(photosdb.persons) - print(photosdb.albums) + print(photosdb.album_names) print(photosdb.keywords_as_dict) print(photosdb.persons_as_dict) diff --git a/examples/examples.py b/examples/examples.py index 83fa1723..38071277 100644 --- a/examples/examples.py +++ b/examples/examples.py @@ -14,7 +14,7 @@ def main(): print(photosdb.keywords) print(photosdb.persons) - print(photosdb.albums) + print(photosdb.album_names) print(photosdb.keywords_as_dict) print(photosdb.persons_as_dict) diff --git a/osxphotos/_version.py b/osxphotos/_version.py index 86bc46e9..0a9a4b73 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,3 +1,3 @@ """ version info """ -__version__ = "0.25.1" +__version__ = "0.26.0" diff --git a/osxphotos/photosdb.py b/osxphotos/photosdb.py index bb85f4df..c85e6213 100644 --- a/osxphotos/photosdb.py +++ b/osxphotos/photosdb.py @@ -120,6 +120,12 @@ class PhotosDB: # e.g. {'1EB2B765-0765-43BA-A90C-0D0580E6172C': ['0C514A98-7B77-4E4F-801B-364B7B65EAFA']} 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 # 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']} @@ -264,6 +270,7 @@ class PhotosDB: k for k in self._dbalbums_album.keys() if self._dbalbum_details[k]["cloudownerhashedpersonid"] is None + and self._dbalbum_details[k]["intrash"] == 0 ] for k in album_keys: title = self._dbalbum_details[k]["title"] @@ -314,7 +321,7 @@ class PhotosDB: return list(persons) @property - def albums(self): + def album_names(self): """ return list of albums found in photos database """ # Could be more than one album with same name @@ -331,7 +338,7 @@ class PhotosDB: return list(albums) @property - def albums_shared(self): + def album_names_shared(self): """ return list of shared albums found in photos database only valid for Photos 5; on Photos <= 4, prints warning and returns empty list """ @@ -466,9 +473,9 @@ class PhotosDB: "uuid, " # 0 "name, " # 1 "cloudLibraryState, " # 2 - "cloudIdentifier " # 3 + "cloudIdentifier, " # 3 + "isInTrash " # 4 "FROM RKAlbum " - "WHERE isInTrash = 0" ) for album in c: @@ -476,6 +483,7 @@ class PhotosDB: "title": album[1], "cloudlibrarystate": album[2], "cloudidentifier": album[3], + "intrash": album[4], "cloudlocalstate": None, # Photos 5 "cloudownerfirstname": None, # Photos 5 "cloudownderlastname": None, # Photos 5 @@ -846,12 +854,10 @@ class PhotosDB: "FROM RKPlaceForVersion " f"WHERE versionId = '{self._dbphotos[uuid]['modelID']}'" ) - + place_ids = [id[0] for id in place_ids_query.fetchall()] - self._dbphotos[uuid]["placeIDs"] = place_ids - country_code = [ - countries[x] for x in place_ids if x in countries - ] + self._dbphotos[uuid]["placeIDs"] = place_ids + country_code = [countries[x] for x in place_ids if x in countries] if len(country_code) > 1: logging.warning(f"Found more than one country code for uuid: {uuid}") @@ -860,7 +866,7 @@ class PhotosDB: else: self._dbphotos[uuid]["countryCode"] = None - # get the place info that matches the RKPlace modelIDs for this photo + # get the place info that matches the RKPlace modelIDs for this photo # (place_ids), sort by area (element 3 of the place_data tuple in places) place_names = [ pname @@ -986,6 +992,7 @@ class PhotosDB: logging.debug(pformat(self._dbfaces_person)) logging.debug(self._dbfaces_uuid) + # get details about albums c.execute( "SELECT ZGENERICALBUM.ZUUID, ZGENERICASSET.ZUUID " "FROM ZGENERICASSET " @@ -995,12 +1002,15 @@ class PhotosDB: ) for album in c: # store by uuid in _dbalbums_uuid and by album in _dbalbums_album - if not album[1] in self._dbalbums_uuid: - self._dbalbums_uuid[album[1]] = [] - if not album[0] in self._dbalbums_album: - self._dbalbums_album[album[0]] = [] - self._dbalbums_uuid[album[1]].append(album[0]) - self._dbalbums_album[album[0]].append(album[1]) + try: + self._dbalbums_uuid[album[1]].append(album[0]) + except KeyError: + self._dbalbums_uuid[album[1]] = [album[0]] + + try: + self._dbalbums_album[album[0]].append(album[1]) + except KeyError: + self._dbalbums_album[album[0]] = [album[1]] # now get additional details about albums c.execute( @@ -1010,8 +1020,12 @@ class PhotosDB: "ZCLOUDLOCALSTATE, " # 2 "ZCLOUDOWNERFIRSTNAME, " # 3 "ZCLOUDOWNERLASTNAME, " # 4 - "ZCLOUDOWNERHASHEDPERSONID " # 5 - "FROM ZGENERICALBUM" + "ZCLOUDOWNERHASHEDPERSONID, " # 5 + "ZKIND, " # 6 + "ZPARENTFOLDER, " # 7 + "Z_PK, " # 8 + "ZTRASHEDSTATE " # 9 + "FROM ZGENERICALBUM " ) for album in c: self._dbalbum_details[album[0]] = { @@ -1021,9 +1035,18 @@ class PhotosDB: "cloudownderlastname": album[4], "cloudownerhashedpersonid": album[5], "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(): logging.debug(f"Finished walking through albums") logging.debug(pformat(self._dbalbums_album)) diff --git a/tests/test_10_12_6.py b/tests/test_10_12_6.py index 91dad97a..bedd8775 100644 --- a/tests/test_10_12_6.py +++ b/tests/test_10_12_6.py @@ -72,13 +72,13 @@ def test_keywords(): assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) -def test_albums(): +def test_album_names(): import osxphotos import collections photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - assert "Pumpkin Farm" in photosdb.albums - assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) + assert "Pumpkin Farm" in photosdb.album_names + assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names) def test_keywords_dict(): diff --git a/tests/test_catalina_10_15_1.py b/tests/test_catalina_10_15_1.py index e0b5582d..86bdacdb 100644 --- a/tests/test_catalina_10_15_1.py +++ b/tests/test_catalina_10_15_1.py @@ -150,13 +150,13 @@ def test_keywords(): assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) -def test_albums(): +def test_album_names(): import osxphotos import collections photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - assert "Pumpkin Farm" in photosdb.albums - assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) + assert "Pumpkin Farm" in photosdb.album_names + assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names) def test_keywords_dict(): diff --git a/tests/test_catalina_10_15_4.py b/tests/test_catalina_10_15_4.py index 40836630..74776ab3 100644 --- a/tests/test_catalina_10_15_4.py +++ b/tests/test_catalina_10_15_4.py @@ -150,13 +150,13 @@ def test_keywords(): assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) -def test_albums(): +def test_album_names(): import osxphotos import collections photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - assert "Pumpkin Farm" in photosdb.albums - assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) + assert "Pumpkin Farm" in photosdb.album_names + assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names) def test_keywords_dict(): @@ -366,7 +366,7 @@ def test_count(): photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photos = photosdb.photos() - assert len(photos) == 8 + assert len(photos) == 8 def test_keyword_2(): @@ -772,7 +772,7 @@ def test_from_to_date(): assert len(photos) == 2 photos = photosdb.photos(to_date=dt.datetime(2018, 10, 28)) - assert len(photos) == 6 + assert len(photos) == 6 photos = photosdb.photos( from_date=dt.datetime(2018, 9, 28), to_date=dt.datetime(2018, 9, 29) diff --git a/tests/test_empty_library_4_0.py b/tests/test_empty_library_4_0.py index 837764c5..027f57a3 100644 --- a/tests/test_empty_library_4_0.py +++ b/tests/test_empty_library_4_0.py @@ -52,12 +52,12 @@ def test_keywords(): assert photosdb.keywords == [] -def test_albums(): +def test_album_names(): import osxphotos import collections photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - assert photosdb.albums == [] + assert photosdb.album_names == [] def test_keywords_dict(): diff --git a/tests/test_highsierra.py b/tests/test_highsierra.py index c4f8a28f..916893ec 100644 --- a/tests/test_highsierra.py +++ b/tests/test_highsierra.py @@ -71,13 +71,13 @@ def test_keywords(): assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) -def test_albums(): +def test_album_names(): import osxphotos import collections photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - assert "Pumpkin Farm" in photosdb.albums - assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) + assert "Pumpkin Farm" in photosdb.album_names + assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names) def test_keywords_dict(): diff --git a/tests/test_mojave_10_14_5.py b/tests/test_mojave_10_14_5.py index 2cc090a2..b4b7806a 100644 --- a/tests/test_mojave_10_14_5.py +++ b/tests/test_mojave_10_14_5.py @@ -71,13 +71,13 @@ def test_keywords(): assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) -def test_albums(): +def test_album_names(): import osxphotos import collections photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - assert "Pumpkin Farm" in photosdb.albums - assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) + assert "Pumpkin Farm" in photosdb.album_names + assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names) def test_keywords_dict(): diff --git a/tests/test_mojave_10_14_6.py b/tests/test_mojave_10_14_6.py index 180834e0..77edda43 100644 --- a/tests/test_mojave_10_14_6.py +++ b/tests/test_mojave_10_14_6.py @@ -76,13 +76,13 @@ def test_keywords(): assert collections.Counter(KEYWORDS) == collections.Counter(photosdb.keywords) -def test_albums(): +def test_album_names(): import osxphotos import collections photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - assert "Pumpkin Farm" in photosdb.albums - assert collections.Counter(ALBUMS) == collections.Counter(photosdb.albums) + assert "Pumpkin Farm" in photosdb.album_names + assert collections.Counter(ALBUMS) == collections.Counter(photosdb.album_names) def test_keywords_dict(): diff --git a/tests/test_shared_catalina_10_15_1.py b/tests/test_shared_catalina_10_15_1.py index be9d4ffd..c8b2f12e 100644 --- a/tests/test_shared_catalina_10_15_1.py +++ b/tests/test_shared_catalina_10_15_1.py @@ -26,11 +26,11 @@ UUID_SHARED = [ UUID_NOT_SHARED = ["37210110-E940-4227-92D3-45C40F68EB0A"] -def test_albums(): +def test_album_names(): import osxphotos photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - albums = photosdb.albums + albums = photosdb.album_names assert len(albums) == 1 assert albums[0] == ALBUMS[0] @@ -40,7 +40,7 @@ def test_albums_shared(): import osxphotos photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - albums_shared = photosdb.albums_shared + albums_shared = photosdb.album_names_shared assert len(albums_shared) == 1 assert albums_shared[0] == ALBUMS_SHARED[0] diff --git a/tests/test_shared_mojave_10_14_6.py b/tests/test_shared_mojave_10_14_6.py index d33aedcc..94b6f64f 100644 --- a/tests/test_shared_mojave_10_14_6.py +++ b/tests/test_shared_mojave_10_14_6.py @@ -11,22 +11,22 @@ ALBUMS = ["Pumpkin Farm", "Test Album", "Test Album (1)"] ALBUM_DICT = {"Pumpkin Farm": 3, "Test Album": 1, "Test Album (1)": 1} -def test_albums(): +def test_album_names(): import osxphotos photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - albums = photosdb.albums + albums = photosdb.album_names assert len(albums) == len(ALBUMS) for album in albums: assert album in ALBUMS -def test_albums_shared(): +def test_albums_names_shared(): import osxphotos photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) - albums_shared = photosdb.albums_shared + albums_shared = photosdb.album_names_shared assert len(albums_shared) == 0 @@ -65,4 +65,3 @@ def test_not_shared(): photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photos = [p for p in photosdb.photos() if not p.shared] assert len(photos) == 7 -