Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bac106eb7 | ||
|
|
47d1c82c03 | ||
|
|
6f281711e2 | ||
|
|
4b30b3b426 | ||
|
|
1fa9583ea6 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. Dates are d
|
|||||||
|
|
||||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
#### [v0.28.1](https://github.com/RhetTbull/osxphotos/compare/v0.27.4...v0.28.1)
|
||||||
|
|
||||||
|
> 18 April 2020
|
||||||
|
|
||||||
|
- Initial work on suppport for associated RAW images [`7e42ebb`](https://github.com/RhetTbull/osxphotos/commit/7e42ebb2402d45cd5d20bdd55bddddaa9db4679f)
|
||||||
|
- Initial support for RAW photos in Photos 4 to address issue #101 [`9d15147`](https://github.com/RhetTbull/osxphotos/commit/9d151478d610291b8d482aafae3d445dfd391fca)
|
||||||
|
- replaced CLI option --original-name with --current-name [`36c2821`](https://github.com/RhetTbull/osxphotos/commit/36c2821a0fa62eaaa54cf1edc2d9c6da98155354)
|
||||||
|
|
||||||
#### [v0.27.4](https://github.com/RhetTbull/osxphotos/compare/v0.27.3...v0.27.4)
|
#### [v0.27.4](https://github.com/RhetTbull/osxphotos/compare/v0.27.3...v0.27.4)
|
||||||
|
|
||||||
> 12 April 2020
|
> 12 April 2020
|
||||||
@@ -265,11 +273,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|||||||
- Moved PhotosDB attributes to properties instead of methods [`d95acdf`](https://github.com/RhetTbull/osxphotos/commit/d95acdf9f8764a1720bcba71a6dad29bf668eaf9)
|
- Moved PhotosDB attributes to properties instead of methods [`d95acdf`](https://github.com/RhetTbull/osxphotos/commit/d95acdf9f8764a1720bcba71a6dad29bf668eaf9)
|
||||||
- changed interface for export, prepped for exiftool_json_sidecar [`1fe8859`](https://github.com/RhetTbull/osxphotos/commit/1fe885962e8a9a420e776bdd3dc640ca143224b2)
|
- changed interface for export, prepped for exiftool_json_sidecar [`1fe8859`](https://github.com/RhetTbull/osxphotos/commit/1fe885962e8a9a420e776bdd3dc640ca143224b2)
|
||||||
|
|
||||||
#### [v0.15.1](https://github.com/RhetTbull/osxphotos/compare/v0.15.0...v0.15.1)
|
#### [v0.15.1](https://github.com/RhetTbull/osxphotos/compare/v0.14.21...v0.15.1)
|
||||||
|
|
||||||
> 13 April 2020
|
|
||||||
|
|
||||||
#### [v0.15.0](https://github.com/RhetTbull/osxphotos/compare/v0.14.21...v0.15.0)
|
|
||||||
|
|
||||||
> 14 December 2019
|
> 14 December 2019
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ _TESTED_DB_VERSIONS = ["6000", "4025", "4016", "3301", "2622"]
|
|||||||
_PHOTOS_3_VERSION = "3301"
|
_PHOTOS_3_VERSION = "3301"
|
||||||
|
|
||||||
# versions 5.0 and later have a different database structure
|
# versions 5.0 and later have a different database structure
|
||||||
_PHOTOS_4_VERSION = "4025" # latest Mojove version on 10.14.6
|
_PHOTOS_4_VERSION = "4025" # latest Mojove version on 10.14.6
|
||||||
_PHOTOS_5_VERSION = "6000" # seems to be current on 10.15.1 through 10.14.4
|
_PHOTOS_5_VERSION = "6000" # seems to be current on 10.15.1 through 10.15.4
|
||||||
|
|
||||||
# which major version operating systems have been tested
|
# which major version operating systems have been tested
|
||||||
_TESTED_OS_VERSIONS = ["12", "13", "14", "15"]
|
_TESTED_OS_VERSIONS = ["12", "13", "14", "15"]
|
||||||
@@ -47,3 +47,7 @@ _PHOTOS_5_ALBUM_KIND = 2 # normal user album
|
|||||||
_PHOTOS_5_SHARED_ALBUM_KIND = 1505 # shared album
|
_PHOTOS_5_SHARED_ALBUM_KIND = 1505 # shared album
|
||||||
_PHOTOS_5_FOLDER_KIND = 4000 # user folder
|
_PHOTOS_5_FOLDER_KIND = 4000 # user folder
|
||||||
_PHOTOS_5_ROOT_FOLDER_KIND = 3999 # root folder
|
_PHOTOS_5_ROOT_FOLDER_KIND = 3999 # root folder
|
||||||
|
|
||||||
|
_PHOTOS_4_ALBUM_KIND = 3 # RKAlbum.albumSubclass
|
||||||
|
_PHOTOS_4_TOP_LEVEL_ALBUM = "TopLevelAlbums"
|
||||||
|
_PHOTOS_4_ROOT_FOLDER = "LibraryFolder"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.28.1"
|
__version__ = "0.28.2"
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ PhotosDB.folders() returns a list of FolderInfo objects
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ._constants import _PHOTOS_5_ALBUM_KIND, _PHOTOS_5_FOLDER_KIND, _PHOTOS_5_VERSION
|
from ._constants import (
|
||||||
|
_PHOTOS_4_ALBUM_KIND,
|
||||||
|
_PHOTOS_4_TOP_LEVEL_ALBUM,
|
||||||
|
_PHOTOS_4_VERSION,
|
||||||
|
_PHOTOS_5_ALBUM_KIND,
|
||||||
|
_PHOTOS_5_FOLDER_KIND,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AlbumInfo:
|
class AlbumInfo:
|
||||||
@@ -53,14 +59,13 @@ class AlbumInfo:
|
|||||||
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
||||||
returns empty list if album is not in any folders """
|
returns empty list if album is not in any folders """
|
||||||
|
|
||||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
|
||||||
logging.warning("Folders not yet implemented for this DB version")
|
|
||||||
return []
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self._folder_names
|
return self._folder_names
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self._folder_names = self._db._album_folder_hierarchy_list(self._uuid)
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
|
self._folder_names = self._db._album_folder_hierarchy_list(self._uuid)
|
||||||
|
else:
|
||||||
|
self._folder_names = self._db._album_folder_hierarchy_list(self._uuid)
|
||||||
return self._folder_names
|
return self._folder_names
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -70,10 +75,6 @@ class AlbumInfo:
|
|||||||
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
||||||
returns empty list if album is not in any folders """
|
returns empty list if album is not in any folders """
|
||||||
|
|
||||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
|
||||||
logging.warning("Folders not yet implemented for this DB version")
|
|
||||||
return []
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self._folders
|
return self._folders
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -83,19 +84,23 @@ class AlbumInfo:
|
|||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
""" returns FolderInfo object for parent folder or None if no parent (e.g. top-level album) """
|
""" returns FolderInfo object for parent folder or None if no parent (e.g. top-level album) """
|
||||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
|
||||||
logging.warning("Folders not yet implemented for this DB version")
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self._parent
|
return self._parent
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
parent_pk = self._db._dbalbum_details[self._uuid]["parentfolder"]
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
self._parent = (
|
parent_uuid = self._db._dbalbum_details[self._uuid]["folderUuid"]
|
||||||
FolderInfo(db=self._db, uuid=self._db._dbalbums_pk[parent_pk])
|
self._parent = (
|
||||||
if parent_pk != self._db._folder_root_pk
|
FolderInfo(db=self._db, uuid=parent_uuid)
|
||||||
else None
|
if parent_uuid != _PHOTOS_4_TOP_LEVEL_ALBUM
|
||||||
)
|
else None
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
parent_pk = self._db._dbalbum_details[self._uuid]["parentfolder"]
|
||||||
|
self._parent = (
|
||||||
|
FolderInfo(db=self._db, uuid=self._db._dbalbums_pk[parent_pk])
|
||||||
|
if parent_pk != self._db._folder_root_pk
|
||||||
|
else None
|
||||||
|
)
|
||||||
return self._parent
|
return self._parent
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
@@ -112,8 +117,12 @@ class FolderInfo:
|
|||||||
def __init__(self, db=None, uuid=None):
|
def __init__(self, db=None, uuid=None):
|
||||||
self._uuid = uuid
|
self._uuid = uuid
|
||||||
self._db = db
|
self._db = db
|
||||||
self._pk = self._db._dbalbum_details[uuid]["pk"]
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
self._title = self._db._dbalbum_details[uuid]["title"]
|
self._pk = None
|
||||||
|
self._title = self._db._dbfolder_details[uuid]["name"]
|
||||||
|
else:
|
||||||
|
self._pk = self._db._dbalbum_details[uuid]["pk"]
|
||||||
|
self._title = self._db._dbalbum_details[uuid]["title"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self):
|
def title(self):
|
||||||
@@ -131,13 +140,22 @@ class FolderInfo:
|
|||||||
try:
|
try:
|
||||||
return self._albums
|
return self._albums
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
albums = [
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
AlbumInfo(db=self._db, uuid=album)
|
albums = [
|
||||||
for album, detail in self._db._dbalbum_details.items()
|
AlbumInfo(db=self._db, uuid=album)
|
||||||
if not detail["intrash"]
|
for album, detail in self._db._dbalbum_details.items()
|
||||||
and detail["kind"] == _PHOTOS_5_ALBUM_KIND
|
if not detail["intrash"]
|
||||||
and detail["parentfolder"] == self._pk
|
and detail["albumSubclass"] == _PHOTOS_4_ALBUM_KIND
|
||||||
]
|
and detail["folderUuid"] == self._uuid
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
albums = [
|
||||||
|
AlbumInfo(db=self._db, uuid=album)
|
||||||
|
for album, detail in self._db._dbalbum_details.items()
|
||||||
|
if not detail["intrash"]
|
||||||
|
and detail["kind"] == _PHOTOS_5_ALBUM_KIND
|
||||||
|
and detail["parentfolder"] == self._pk
|
||||||
|
]
|
||||||
self._albums = albums
|
self._albums = albums
|
||||||
return self._albums
|
return self._albums
|
||||||
|
|
||||||
@@ -147,12 +165,20 @@ class FolderInfo:
|
|||||||
try:
|
try:
|
||||||
return self._parent
|
return self._parent
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
parent_pk = self._db._dbalbum_details[self._uuid]["parentfolder"]
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
self._parent = (
|
parent_uuid = self._db._dbfolder_details[self._uuid]["parentFolderUuid"]
|
||||||
FolderInfo(db=self._db, uuid=self._db._dbalbums_pk[parent_pk])
|
self._parent = (
|
||||||
if parent_pk != self._db._folder_root_pk
|
FolderInfo(db=self._db, uuid=parent_uuid)
|
||||||
else None
|
if parent_uuid != _PHOTOS_4_TOP_LEVEL_ALBUM
|
||||||
)
|
else None
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
parent_pk = self._db._dbalbum_details[self._uuid]["parentfolder"]
|
||||||
|
self._parent = (
|
||||||
|
FolderInfo(db=self._db, uuid=self._db._dbalbums_pk[parent_pk])
|
||||||
|
if parent_pk != self._db._folder_root_pk
|
||||||
|
else None
|
||||||
|
)
|
||||||
return self._parent
|
return self._parent
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -161,13 +187,22 @@ class FolderInfo:
|
|||||||
try:
|
try:
|
||||||
return self._folders
|
return self._folders
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
folders = [
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
FolderInfo(db=self._db, uuid=album)
|
folders = [
|
||||||
for album, detail in self._db._dbalbum_details.items()
|
FolderInfo(db=self._db, uuid=folder)
|
||||||
if not detail["intrash"]
|
for folder, detail in self._db._dbfolder_details.items()
|
||||||
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
if not detail["intrash"]
|
||||||
and detail["parentfolder"] == self._pk
|
and not detail["isMagic"]
|
||||||
]
|
and detail["parentFolderUuid"] == self._uuid
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
folders = [
|
||||||
|
FolderInfo(db=self._db, uuid=album)
|
||||||
|
for album, detail in self._db._dbalbum_details.items()
|
||||||
|
if not detail["intrash"]
|
||||||
|
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
||||||
|
and detail["parentfolder"] == self._pk
|
||||||
|
]
|
||||||
self._folders = folders
|
self._folders = folders
|
||||||
return self._folders
|
return self._folders
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ from mako.template import Template
|
|||||||
from ._constants import (
|
from ._constants import (
|
||||||
_MOVIE_TYPE,
|
_MOVIE_TYPE,
|
||||||
_PHOTO_TYPE,
|
_PHOTO_TYPE,
|
||||||
|
_PHOTOS_4_VERSION,
|
||||||
_PHOTOS_5_SHARED_PHOTO_PATH,
|
_PHOTOS_5_SHARED_PHOTO_PATH,
|
||||||
_PHOTOS_5_VERSION,
|
|
||||||
_TEMPLATE_DIR,
|
_TEMPLATE_DIR,
|
||||||
_XMP_TEMPLATE_NAME,
|
_XMP_TEMPLATE_NAME,
|
||||||
)
|
)
|
||||||
@@ -98,7 +98,7 @@ class PhotoInfo:
|
|||||||
if self._info["isMissing"] == 1:
|
if self._info["isMissing"] == 1:
|
||||||
return photopath # path would be meaningless until downloaded
|
return photopath # path would be meaningless until downloaded
|
||||||
|
|
||||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
vol = self._info["volume"]
|
vol = self._info["volume"]
|
||||||
if vol is not None:
|
if vol is not None:
|
||||||
photopath = os.path.join("/Volumes", vol, self._info["imagePath"])
|
photopath = os.path.join("/Volumes", vol, self._info["imagePath"])
|
||||||
@@ -137,7 +137,7 @@ class PhotoInfo:
|
|||||||
|
|
||||||
photopath = None
|
photopath = None
|
||||||
|
|
||||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
if self._info["hasAdjustments"]:
|
if self._info["hasAdjustments"]:
|
||||||
edit_id = self._info["edit_resource_id"]
|
edit_id = self._info["edit_resource_id"]
|
||||||
if edit_id is not None:
|
if edit_id is not None:
|
||||||
@@ -269,7 +269,7 @@ class PhotoInfo:
|
|||||||
# )
|
# )
|
||||||
# return photopath
|
# return photopath
|
||||||
|
|
||||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
vol = self._info["raw_info"]["volume"]
|
vol = self._info["raw_info"]["volume"]
|
||||||
if vol is not None:
|
if vol is not None:
|
||||||
photopath = os.path.join(
|
photopath = os.path.join(
|
||||||
@@ -400,7 +400,7 @@ class PhotoInfo:
|
|||||||
def shared(self):
|
def shared(self):
|
||||||
""" returns True if photos is in a shared iCloud album otherwise false
|
""" returns True if photos is in a shared iCloud album otherwise false
|
||||||
Only valid on Photos 5; returns None on older versions """
|
Only valid on Photos 5; returns None on older versions """
|
||||||
if self._db._db_version >= _PHOTOS_5_VERSION:
|
if self._db._db_version > _PHOTOS_4_VERSION:
|
||||||
return self._info["shared"]
|
return self._info["shared"]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
@@ -445,7 +445,7 @@ class PhotoInfo:
|
|||||||
""" Returns True if photo is a cloud asset (in an iCloud library),
|
""" Returns True if photo is a cloud asset (in an iCloud library),
|
||||||
otherwise False
|
otherwise False
|
||||||
"""
|
"""
|
||||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
return (
|
return (
|
||||||
True
|
True
|
||||||
if self._info["cloudLibraryState"] is not None
|
if self._info["cloudLibraryState"] is not None
|
||||||
@@ -488,7 +488,7 @@ class PhotoInfo:
|
|||||||
If photo is missing, returns None """
|
If photo is missing, returns None """
|
||||||
|
|
||||||
photopath = None
|
photopath = None
|
||||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
if self.live_photo and not self.ismissing:
|
if self.live_photo and not self.ismissing:
|
||||||
live_model_id = self._info["live_model_id"]
|
live_model_id = self._info["live_model_id"]
|
||||||
if live_model_id == None:
|
if live_model_id == None:
|
||||||
@@ -579,7 +579,7 @@ class PhotoInfo:
|
|||||||
# implementation note: doesn't create the PlaceInfo object until requested
|
# implementation note: doesn't create the PlaceInfo object until requested
|
||||||
# then memoizes the object in self._place to avoid recreating the object
|
# then memoizes the object in self._place to avoid recreating the object
|
||||||
|
|
||||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||||
try:
|
try:
|
||||||
return self._place # pylint: disable=access-member-before-definition
|
return self._place # pylint: disable=access-member-before-definition
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -717,8 +717,11 @@ class PhotoInfo:
|
|||||||
# warn if suffixes don't match but ignore .JPG / .jpeg as
|
# warn if suffixes don't match but ignore .JPG / .jpeg as
|
||||||
# Photo's often converts .JPG to .jpeg
|
# Photo's often converts .JPG to .jpeg
|
||||||
suffixes = sorted([x.lower() for x in [dest.suffix, actual_suffix]])
|
suffixes = sorted([x.lower() for x in [dest.suffix, actual_suffix]])
|
||||||
if dest.suffix != actual_suffix and suffixes != [".jpeg", ".jpg"]:
|
if dest.suffix.lower() != actual_suffix.lower() and suffixes != [
|
||||||
logging.debug(
|
".jpeg",
|
||||||
|
".jpg",
|
||||||
|
]:
|
||||||
|
logging.warning(
|
||||||
f"Invalid destination suffix: {dest.suffix}, should be {actual_suffix}"
|
f"Invalid destination suffix: {dest.suffix}, should be {actual_suffix}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ from ._constants import (
|
|||||||
_TESTED_DB_VERSIONS,
|
_TESTED_DB_VERSIONS,
|
||||||
_TESTED_OS_VERSIONS,
|
_TESTED_OS_VERSIONS,
|
||||||
_UNKNOWN_PERSON,
|
_UNKNOWN_PERSON,
|
||||||
|
_PHOTOS_4_ALBUM_KIND,
|
||||||
|
_PHOTOS_4_TOP_LEVEL_ALBUM,
|
||||||
_PHOTOS_5_ROOT_FOLDER_KIND,
|
_PHOTOS_5_ROOT_FOLDER_KIND,
|
||||||
_PHOTOS_5_FOLDER_KIND,
|
_PHOTOS_5_FOLDER_KIND,
|
||||||
_PHOTOS_5_ALBUM_KIND,
|
_PHOTOS_5_ALBUM_KIND,
|
||||||
@@ -173,6 +175,9 @@ class PhotosDB:
|
|||||||
# e.g. {'AA4145F5-098C-496E-9197-B7584958FF9B': {'99D24D3E-59E7-465F-B386-A48A94B00BC1': {'F2246D82-1A12-4994-9654-3DC6FE38A7A8': None}}, }
|
# e.g. {'AA4145F5-098C-496E-9197-B7584958FF9B': {'99D24D3E-59E7-465F-B386-A48A94B00BC1': {'F2246D82-1A12-4994-9654-3DC6FE38A7A8': None}}, }
|
||||||
self._dbalbum_folders = {}
|
self._dbalbum_folders = {}
|
||||||
|
|
||||||
|
# Dict with information about folders
|
||||||
|
self._dbfolder_details = {}
|
||||||
|
|
||||||
# Will hold the primary key of root folder
|
# Will hold the primary key of root folder
|
||||||
self._folder_root_pk = None
|
self._folder_root_pk = None
|
||||||
|
|
||||||
@@ -312,7 +317,7 @@ class PhotosDB:
|
|||||||
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 _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
|
||||||
if self._db_version < _PHOTOS_5_VERSION:
|
if self._db_version <= _PHOTOS_4_VERSION:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
|
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
|
||||||
)
|
)
|
||||||
@@ -348,33 +353,43 @@ class PhotosDB:
|
|||||||
@property
|
@property
|
||||||
def folder_info(self):
|
def folder_info(self):
|
||||||
""" return list FolderInfo objects representing top-level folders in the photos database """
|
""" return list FolderInfo objects representing top-level folders in the photos database """
|
||||||
if self._db_version < _PHOTOS_5_VERSION:
|
if self._db_version <= _PHOTOS_4_VERSION:
|
||||||
logging.warning("Folders not yet implemented for this DB version")
|
folders = [
|
||||||
return []
|
FolderInfo(db=self, uuid=folder)
|
||||||
|
for folder, detail in self._dbfolder_details.items()
|
||||||
folders = [
|
if not detail["intrash"]
|
||||||
FolderInfo(db=self, uuid=album)
|
and not detail["isMagic"]
|
||||||
for album, detail in self._dbalbum_details.items()
|
and detail["parentFolderUuid"] == _PHOTOS_4_TOP_LEVEL_ALBUM
|
||||||
if not detail["intrash"]
|
]
|
||||||
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
else:
|
||||||
and detail["parentfolder"] == self._folder_root_pk
|
folders = [
|
||||||
]
|
FolderInfo(db=self, uuid=album)
|
||||||
|
for album, detail in self._dbalbum_details.items()
|
||||||
|
if not detail["intrash"]
|
||||||
|
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
||||||
|
and detail["parentfolder"] == self._folder_root_pk
|
||||||
|
]
|
||||||
return folders
|
return folders
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def folders(self):
|
def folders(self):
|
||||||
""" return list of top-level folder names in the photos database """
|
""" return list of top-level folder names in the photos database """
|
||||||
if self._db_version < _PHOTOS_5_VERSION:
|
if self._db_version <= _PHOTOS_4_VERSION:
|
||||||
logging.warning("Folders not yet implemented for this DB version")
|
folder_names = [
|
||||||
return []
|
folder["name"]
|
||||||
|
for folder in self._dbfolder_details.values()
|
||||||
folder_names = [
|
if not folder["intrash"]
|
||||||
detail["title"]
|
and not folder["isMagic"]
|
||||||
for detail in self._dbalbum_details.values()
|
and folder["parentFolderUuid"] == _PHOTOS_4_TOP_LEVEL_ALBUM
|
||||||
if not detail["intrash"]
|
]
|
||||||
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
else:
|
||||||
and detail["parentfolder"] == self._folder_root_pk
|
folder_names = [
|
||||||
]
|
detail["title"]
|
||||||
|
for detail in self._dbalbum_details.values()
|
||||||
|
if not detail["intrash"]
|
||||||
|
and detail["kind"] == _PHOTOS_5_FOLDER_KIND
|
||||||
|
and detail["parentfolder"] == self._folder_root_pk
|
||||||
|
]
|
||||||
return folder_names
|
return folder_names
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -395,7 +410,7 @@ class PhotosDB:
|
|||||||
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
|
||||||
|
|
||||||
if self._db_version < _PHOTOS_5_VERSION:
|
if self._db_version <= _PHOTOS_4_VERSION:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
|
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
|
||||||
)
|
)
|
||||||
@@ -434,7 +449,7 @@ 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_5_VERSION:
|
if self._db_version <= _PHOTOS_4_VERSION:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"album_names_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
|
f"album_names_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
|
||||||
)
|
)
|
||||||
@@ -521,10 +536,10 @@ class PhotosDB:
|
|||||||
|
|
||||||
# Look for all combinations of persons and pictures
|
# Look for all combinations of persons and pictures
|
||||||
c.execute(
|
c.execute(
|
||||||
"select RKPerson.name, RKVersion.uuid from RKFace, RKPerson, RKVersion, RKMaster "
|
""" select RKPerson.name, RKVersion.uuid from RKFace, RKPerson, RKVersion, RKMaster
|
||||||
+ "where RKFace.personID = RKperson.modelID and RKVersion.modelId = RKFace.ImageModelId "
|
where RKFace.personID = RKperson.modelID and RKVersion.modelId = RKFace.ImageModelId
|
||||||
+ "and RKVersion.masterUuid = RKMaster.uuid and "
|
and RKVersion.masterUuid = RKMaster.uuid
|
||||||
+ "RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
and RKVersion.isInTrash = 0 """
|
||||||
)
|
)
|
||||||
for person in c:
|
for person in c:
|
||||||
if person[0] is None:
|
if person[0] is None:
|
||||||
@@ -538,10 +553,13 @@ class PhotosDB:
|
|||||||
|
|
||||||
# Get info on albums
|
# Get info on albums
|
||||||
c.execute(
|
c.execute(
|
||||||
"select RKAlbum.uuid, RKVersion.uuid from RKAlbum, RKVersion, RKAlbumVersion "
|
""" select
|
||||||
+ "where RKAlbum.modelID = RKAlbumVersion.albumId and "
|
RKAlbum.uuid,
|
||||||
+ "RKAlbumVersion.versionID = RKVersion.modelId and "
|
RKVersion.uuid
|
||||||
+ "RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
from RKAlbum, RKVersion, RKAlbumVersion
|
||||||
|
where RKAlbum.modelID = RKAlbumVersion.albumId and
|
||||||
|
RKAlbumVersion.versionID = RKVersion.modelId
|
||||||
|
and RKVersion.isInTrash = 0 """
|
||||||
)
|
)
|
||||||
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
|
||||||
@@ -554,17 +572,31 @@ class PhotosDB:
|
|||||||
|
|
||||||
# now get additional details about albums
|
# now get additional details about albums
|
||||||
c.execute(
|
c.execute(
|
||||||
"SELECT "
|
""" SELECT
|
||||||
"uuid, " # 0
|
uuid,
|
||||||
"name, " # 1
|
name,
|
||||||
"cloudLibraryState, " # 2
|
cloudLibraryState,
|
||||||
"cloudIdentifier, " # 3
|
cloudIdentifier,
|
||||||
"isInTrash " # 4
|
isInTrash,
|
||||||
"FROM RKAlbum "
|
folderUuid,
|
||||||
|
albumType,
|
||||||
|
albumSubclass
|
||||||
|
FROM RKAlbum """
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Order of results
|
||||||
|
# 0: uuid
|
||||||
|
# 1: name
|
||||||
|
# 2: cloudLibraryState
|
||||||
|
# 3: cloudIdentifier
|
||||||
|
# 4: isInTrash
|
||||||
|
# 5: folderUuid
|
||||||
|
# 6: albumType
|
||||||
|
# 7: albumSubclass -- if 3, normal user album
|
||||||
|
|
||||||
for album in c:
|
for album in c:
|
||||||
self._dbalbum_details[album[0]] = {
|
self._dbalbum_details[album[0]] = {
|
||||||
|
"_uuid": album[0],
|
||||||
"title": album[1],
|
"title": album[1],
|
||||||
"cloudlibrarystate": album[2],
|
"cloudlibrarystate": album[2],
|
||||||
"cloudidentifier": album[3],
|
"cloudidentifier": album[3],
|
||||||
@@ -573,22 +605,80 @@ class PhotosDB:
|
|||||||
"cloudownerfirstname": None, # Photos 5
|
"cloudownerfirstname": None, # Photos 5
|
||||||
"cloudownderlastname": None, # Photos 5
|
"cloudownderlastname": None, # Photos 5
|
||||||
"cloudownerhashedpersonid": None, # Photos 5
|
"cloudownerhashedpersonid": None, # Photos 5
|
||||||
|
"folderUuid": album[5],
|
||||||
|
"albumType": album[6],
|
||||||
|
"albumSubclass": album[7],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# get details about folders
|
||||||
|
c.execute(
|
||||||
|
""" SELECT
|
||||||
|
uuid,
|
||||||
|
modelId,
|
||||||
|
name,
|
||||||
|
isMagic,
|
||||||
|
isInTrash,
|
||||||
|
folderType,
|
||||||
|
parentFolderUuid,
|
||||||
|
folderPath
|
||||||
|
FROM RKFolder """
|
||||||
|
)
|
||||||
|
|
||||||
|
# Order of results
|
||||||
|
# 0 uuid,
|
||||||
|
# 1 modelId,
|
||||||
|
# 2 name,
|
||||||
|
# 3 isMagic,
|
||||||
|
# 4 isInTrash,
|
||||||
|
# 5 folderType,
|
||||||
|
# 6 parentFolderUuid,
|
||||||
|
# 7 folderPath
|
||||||
|
|
||||||
|
for row in c:
|
||||||
|
uuid = row[0]
|
||||||
|
self._dbfolder_details[uuid] = {
|
||||||
|
"_uuid": row[0],
|
||||||
|
"modelId": row[1],
|
||||||
|
"name": row[2],
|
||||||
|
"isMagic": row[3],
|
||||||
|
"intrash": row[4],
|
||||||
|
"folderType": row[5],
|
||||||
|
"parentFolderUuid": row[6],
|
||||||
|
"folderPath": row[7],
|
||||||
|
}
|
||||||
|
|
||||||
|
# build _dbalbum_folders in form uuid: [parent uuid] to be consistent with _process_database5
|
||||||
|
for album, details in self._dbalbum_details.items():
|
||||||
|
# album can be in a single folder
|
||||||
|
parent = details["folderUuid"]
|
||||||
|
self._dbalbum_parent_folders[album] = [parent]
|
||||||
|
|
||||||
|
# build folder hierarchy
|
||||||
|
for album, details in self._dbalbum_details.items():
|
||||||
|
parent_folder = details["folderUuid"]
|
||||||
|
if parent_folder != _PHOTOS_4_TOP_LEVEL_ALBUM:
|
||||||
|
# logging.warning(f"album = {details['title']}, parent = {parent_folder}")
|
||||||
|
folder_hierarchy = self._build_album_folder_hierarchy_4(parent_folder)
|
||||||
|
self._dbalbum_folders[album] = folder_hierarchy
|
||||||
|
else:
|
||||||
|
self._dbalbum_folders[album] = {}
|
||||||
|
|
||||||
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))
|
||||||
logging.debug(pformat(self._dbalbums_uuid))
|
logging.debug(pformat(self._dbalbums_uuid))
|
||||||
logging.debug(pformat(self._dbalbum_details))
|
logging.debug(pformat(self._dbalbum_details))
|
||||||
|
logging.debug(pformat(self._dbalbum_folders))
|
||||||
|
logging.debug(pformat(self._dbfolder_details))
|
||||||
|
|
||||||
# Get info on keywords
|
# Get info on keywords
|
||||||
c.execute(
|
c.execute(
|
||||||
"select RKKeyword.name, RKVersion.uuid, RKMaster.uuid from "
|
""" select RKKeyword.name, RKVersion.uuid, RKMaster.uuid from
|
||||||
+ "RKKeyword, RKKeywordForVersion, RKVersion, RKMaster "
|
RKKeyword, RKKeywordForVersion, RKVersion, RKMaster
|
||||||
+ "where RKKeyword.modelId = RKKeyWordForVersion.keywordID and "
|
where RKKeyword.modelId = RKKeyWordForVersion.keywordID and
|
||||||
+ "RKVersion.modelID = RKKeywordForVersion.versionID "
|
RKVersion.modelID = RKKeywordForVersion.versionID and
|
||||||
+ "and RKMaster.uuid = RKVersion.masterUuid "
|
RKMaster.uuid = RKVersion.masterUuid and
|
||||||
+ "and RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
RKVersion.isInTrash = 0 """
|
||||||
)
|
)
|
||||||
for keyword in c:
|
for keyword in c:
|
||||||
if not keyword[1] in self._dbkeywords_uuid:
|
if not keyword[1] in self._dbkeywords_uuid:
|
||||||
@@ -899,13 +989,13 @@ class PhotosDB:
|
|||||||
|
|
||||||
# get details on external edits
|
# get details on external edits
|
||||||
c.execute(
|
c.execute(
|
||||||
"SELECT RKVersion.uuid, "
|
""" SELECT RKVersion.uuid,
|
||||||
"RKVersion.adjustmentUuid, "
|
RKVersion.adjustmentUuid,
|
||||||
"RKAdjustmentData.originator, "
|
RKAdjustmentData.originator,
|
||||||
"RKAdjustmentData.format "
|
RKAdjustmentData.format
|
||||||
"FROM RKVersion, RKAdjustmentData "
|
FROM RKVersion, RKAdjustmentData
|
||||||
"WHERE RKVersion.adjustmentUuid = RKAdjustmentData.uuid "
|
WHERE RKVersion.adjustmentUuid = RKAdjustmentData.uuid
|
||||||
"AND RKVersion.isInTrash = 0"
|
AND RKVersion.isInTrash = 0 """
|
||||||
)
|
)
|
||||||
|
|
||||||
for row in c:
|
for row in c:
|
||||||
@@ -1122,6 +1212,30 @@ class PhotosDB:
|
|||||||
logging.debug("Burst Photos (dbphotos_burst:")
|
logging.debug("Burst Photos (dbphotos_burst:")
|
||||||
logging.debug(pformat(self._dbphotos_burst))
|
logging.debug(pformat(self._dbphotos_burst))
|
||||||
|
|
||||||
|
def _build_album_folder_hierarchy_4(self, uuid, folders=None):
|
||||||
|
""" recursively build folder/album hierarchy
|
||||||
|
uuid: uuid of the album/folder being processed
|
||||||
|
folders: dict holding the folder hierarchy """
|
||||||
|
|
||||||
|
parent_uuid = self._dbfolder_details[uuid]["parentFolderUuid"]
|
||||||
|
|
||||||
|
# logging.warning(f"uuid = {uuid}, parent = {parent_uuid}, folders = {folders}")
|
||||||
|
|
||||||
|
if parent_uuid is None:
|
||||||
|
return folders
|
||||||
|
|
||||||
|
if parent_uuid == _PHOTOS_4_TOP_LEVEL_ALBUM:
|
||||||
|
# at top of hierarchy, we're done
|
||||||
|
return folders
|
||||||
|
|
||||||
|
# recurse to keep building
|
||||||
|
if not folders:
|
||||||
|
# first time building
|
||||||
|
folders = {uuid: None}
|
||||||
|
folders = {parent_uuid: folders}
|
||||||
|
folders = self._build_album_folder_hierarchy_4(parent_uuid, folders=folders)
|
||||||
|
return folders
|
||||||
|
|
||||||
def _process_database5(self):
|
def _process_database5(self):
|
||||||
""" process the Photos database to extract info """
|
""" process the Photos database to extract info """
|
||||||
""" works on Photos version >= 5.0 """
|
""" works on Photos version >= 5.0 """
|
||||||
@@ -1228,6 +1342,8 @@ class PhotosDB:
|
|||||||
self._folder_root_pk = self._dbalbum_details[root_uuid[0]]["pk"]
|
self._folder_root_pk = self._dbalbum_details[root_uuid[0]]["pk"]
|
||||||
|
|
||||||
# build _dbalbum_folders which is in form uuid: [list of parent uuids]
|
# build _dbalbum_folders which is in form uuid: [list of parent uuids]
|
||||||
|
# TODO: look at this code...it works but I think I album can only be in a single folder
|
||||||
|
# which means there's a code path that will never get executed
|
||||||
for album, details in self._dbalbum_details.items():
|
for album, details in self._dbalbum_details.items():
|
||||||
pk_parent = details["parentfolder"]
|
pk_parent = details["parentfolder"]
|
||||||
if pk_parent is None:
|
if pk_parent is None:
|
||||||
@@ -1246,10 +1362,10 @@ class PhotosDB:
|
|||||||
for album, details in self._dbalbum_details.items():
|
for album, details in self._dbalbum_details.items():
|
||||||
# if details["kind"] in [_PHOTOS_5_ALBUM_KIND, _PHOTOS_5_FOLDER_KIND]:
|
# if details["kind"] in [_PHOTOS_5_ALBUM_KIND, _PHOTOS_5_FOLDER_KIND]:
|
||||||
if details["kind"] == _PHOTOS_5_ALBUM_KIND:
|
if details["kind"] == _PHOTOS_5_ALBUM_KIND:
|
||||||
folder_hierarchy = self._build_album_folder_hierarchy(album)
|
folder_hierarchy = self._build_album_folder_hierarchy_5(album)
|
||||||
self._dbalbum_folders[album] = folder_hierarchy
|
self._dbalbum_folders[album] = folder_hierarchy
|
||||||
elif details["kind"] == _PHOTOS_5_SHARED_ALBUM_KIND:
|
elif details["kind"] == _PHOTOS_5_SHARED_ALBUM_KIND:
|
||||||
# shared albums can be in folders
|
# shared albums can't be in folders
|
||||||
self._dbalbum_folders[album] = []
|
self._dbalbum_folders[album] = []
|
||||||
|
|
||||||
if _debug():
|
if _debug():
|
||||||
@@ -1257,6 +1373,7 @@ class PhotosDB:
|
|||||||
logging.debug(pformat(self._dbalbums_album))
|
logging.debug(pformat(self._dbalbums_album))
|
||||||
logging.debug(pformat(self._dbalbums_uuid))
|
logging.debug(pformat(self._dbalbums_uuid))
|
||||||
logging.debug(pformat(self._dbalbum_details))
|
logging.debug(pformat(self._dbalbum_details))
|
||||||
|
logging.debug(pformat(self._dbalbum_folders))
|
||||||
|
|
||||||
# get details on keywords
|
# get details on keywords
|
||||||
c.execute(
|
c.execute(
|
||||||
@@ -1747,14 +1864,11 @@ class PhotosDB:
|
|||||||
logging.debug("Burst Photos (dbphotos_burst:")
|
logging.debug("Burst Photos (dbphotos_burst:")
|
||||||
logging.debug(pformat(self._dbphotos_burst))
|
logging.debug(pformat(self._dbphotos_burst))
|
||||||
|
|
||||||
def _build_album_folder_hierarchy(self, uuid, folders=None):
|
def _build_album_folder_hierarchy_5(self, uuid, folders=None):
|
||||||
""" recursively build folder/album hierarchy
|
""" recursively build folder/album hierarchy
|
||||||
uuid: uuid of the album/folder being processed
|
uuid: uuid of the album/folder being processed
|
||||||
folders: dict holding the folder hierarchy """
|
folders: dict holding the folder hierarchy """
|
||||||
|
|
||||||
if self._db_version < _PHOTOS_5_VERSION:
|
|
||||||
raise AttributeError("Not yet implemented for this DB version")
|
|
||||||
|
|
||||||
# get parent uuid
|
# get parent uuid
|
||||||
parent = self._dbalbum_details[uuid]["parentfolder"]
|
parent = self._dbalbum_details[uuid]["parentfolder"]
|
||||||
|
|
||||||
@@ -1770,10 +1884,50 @@ class PhotosDB:
|
|||||||
|
|
||||||
# recurse to keep building
|
# recurse to keep building
|
||||||
folders = {parent_uuid: folders}
|
folders = {parent_uuid: folders}
|
||||||
folders = self._build_album_folder_hierarchy(parent_uuid, folders=folders)
|
folders = self._build_album_folder_hierarchy_5(parent_uuid, folders=folders)
|
||||||
return folders
|
return folders
|
||||||
|
|
||||||
def _album_folder_hierarchy_list(self, album_uuid):
|
def _album_folder_hierarchy_list(self, album_uuid):
|
||||||
|
if self._db_version <= _PHOTOS_4_VERSION:
|
||||||
|
return self._album_folder_hierarchy_list_4(album_uuid)
|
||||||
|
else:
|
||||||
|
return self._album_folder_hierarchy_list_5(album_uuid)
|
||||||
|
|
||||||
|
def _album_folder_hierarchy_list_4(self, album_uuid):
|
||||||
|
""" return hierarchical list of folder names album_uuid is contained in
|
||||||
|
the folder list is in form:
|
||||||
|
["Top level folder", "sub folder 1", "sub folder 2"]
|
||||||
|
returns empty list of album is not in any folders """
|
||||||
|
# title = photosdb._dbalbum_details[album_uuid]["title"]
|
||||||
|
folders = self._dbalbum_folders[album_uuid]
|
||||||
|
|
||||||
|
def _recurse_folder_hierarchy(folders, hierarchy=[]):
|
||||||
|
""" recursively walk the folders dict to build list of folder hierarchy """
|
||||||
|
if not folders:
|
||||||
|
# empty folder dict (album has no folder hierarchy)
|
||||||
|
return []
|
||||||
|
|
||||||
|
if len(folders) != 1:
|
||||||
|
raise ValueError("Expected only a single key in folders dict")
|
||||||
|
|
||||||
|
folder_uuid = list(folders)[0] # first and only key of dict
|
||||||
|
|
||||||
|
parent_title = self._dbfolder_details[folder_uuid]["name"]
|
||||||
|
hierarchy.append(parent_title)
|
||||||
|
|
||||||
|
folders = folders[folder_uuid]
|
||||||
|
if folders:
|
||||||
|
# still have elements left to recurse
|
||||||
|
hierarchy = _recurse_folder_hierarchy(folders, hierarchy=hierarchy)
|
||||||
|
return hierarchy
|
||||||
|
|
||||||
|
# no elements left to recurse
|
||||||
|
return hierarchy
|
||||||
|
|
||||||
|
hierarchy = _recurse_folder_hierarchy(folders)
|
||||||
|
return hierarchy
|
||||||
|
|
||||||
|
def _album_folder_hierarchy_list_5(self, album_uuid):
|
||||||
""" return hierarchical list of folder names album_uuid is contained in
|
""" return hierarchical list of folder names album_uuid is contained in
|
||||||
the folder list is in form:
|
the folder list is in form:
|
||||||
["Top level folder", "sub folder 1", "sub folder 2"]
|
["Top level folder", "sub folder 1", "sub folder 2"]
|
||||||
@@ -1808,6 +1962,46 @@ class PhotosDB:
|
|||||||
return hierarchy
|
return hierarchy
|
||||||
|
|
||||||
def _album_folder_hierarchy_folderinfo(self, album_uuid):
|
def _album_folder_hierarchy_folderinfo(self, album_uuid):
|
||||||
|
if self._db_version <= _PHOTOS_4_VERSION:
|
||||||
|
return self._album_folder_hierarchy_folderinfo_4(album_uuid)
|
||||||
|
else:
|
||||||
|
return self._album_folder_hierarchy_folderinfo_5(album_uuid)
|
||||||
|
|
||||||
|
def _album_folder_hierarchy_folderinfo_4(self, album_uuid):
|
||||||
|
""" return hierarchical list of FolderInfo objects album_uuid is contained in
|
||||||
|
["Top level folder", "sub folder 1", "sub folder 2"]
|
||||||
|
returns empty list of album is not in any folders """
|
||||||
|
# title = photosdb._dbalbum_details[album_uuid]["title"]
|
||||||
|
folders = self._dbalbum_folders[album_uuid]
|
||||||
|
# logging.warning(f"uuid = {album_uuid}, folder = {folders}")
|
||||||
|
|
||||||
|
def _recurse_folder_hierarchy(folders, hierarchy=[]):
|
||||||
|
""" recursively walk the folders dict to build list of folder hierarchy """
|
||||||
|
# logging.warning(f"folders={folders},hierarchy = {hierarchy}")
|
||||||
|
if not folders:
|
||||||
|
# empty folder dict (album has no folder hierarchy)
|
||||||
|
return []
|
||||||
|
|
||||||
|
if len(folders) != 1:
|
||||||
|
raise ValueError("Expected only a single key in folders dict")
|
||||||
|
|
||||||
|
folder_uuid = list(folders)[0] # first and only key of dict
|
||||||
|
hierarchy.append(FolderInfo(db=self, uuid=folder_uuid))
|
||||||
|
|
||||||
|
folders = folders[folder_uuid]
|
||||||
|
if folders:
|
||||||
|
# still have elements left to recurse
|
||||||
|
hierarchy = _recurse_folder_hierarchy(folders, hierarchy=hierarchy)
|
||||||
|
return hierarchy
|
||||||
|
|
||||||
|
# no elements left to recurse
|
||||||
|
return hierarchy
|
||||||
|
|
||||||
|
hierarchy = _recurse_folder_hierarchy(folders)
|
||||||
|
# logging.warning(f"hierarchy = {hierarchy}")
|
||||||
|
return hierarchy
|
||||||
|
|
||||||
|
def _album_folder_hierarchy_folderinfo_5(self, album_uuid):
|
||||||
""" return hierarchical list of FolderInfo objects album_uuid is contained in
|
""" return hierarchical list of FolderInfo objects album_uuid is contained in
|
||||||
["Top level folder", "sub folder 1", "sub folder 2"]
|
["Top level folder", "sub folder 1", "sub folder 2"]
|
||||||
returns empty list of album is not in any folders """
|
returns empty list of album is not in any folders """
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -9,12 +9,14 @@
|
|||||||
<key>ExpandedSidebarItemIdentifiers</key>
|
<key>ExpandedSidebarItemIdentifiers</key>
|
||||||
<array>
|
<array>
|
||||||
<string>TopLevelAlbums</string>
|
<string>TopLevelAlbums</string>
|
||||||
|
<string>QtSnVvTkQ%i2z3hB834M1A</string>
|
||||||
<string>TopLevelSlideshows</string>
|
<string>TopLevelSlideshows</string>
|
||||||
|
<string>N7eQ4VhfTfeHFp9PPHaJDw</string>
|
||||||
</array>
|
</array>
|
||||||
<key>IPXWorkspaceControllerZoomLevelsKey</key>
|
<key>IPXWorkspaceControllerZoomLevelsKey</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>kZoomLevelIdentifierAlbums</key>
|
<key>kZoomLevelIdentifierAlbums</key>
|
||||||
<integer>10</integer>
|
<integer>5</integer>
|
||||||
<key>kZoomLevelIdentifierVersions</key>
|
<key>kZoomLevelIdentifierVersions</key>
|
||||||
<integer>7</integer>
|
<integer>7</integer>
|
||||||
</dict>
|
</dict>
|
||||||
@@ -23,11 +25,11 @@
|
|||||||
<key>key</key>
|
<key>key</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>lastKnownDisplayName</key>
|
<key>lastKnownDisplayName</key>
|
||||||
<string>Test Album (1)</string>
|
<string>Pumpkin Farm (1)</string>
|
||||||
<key>type</key>
|
<key>type</key>
|
||||||
<string>album</string>
|
<string>album</string>
|
||||||
<key>uuid</key>
|
<key>uuid</key>
|
||||||
<string>Uq6qsKihRRSjMHTiD+0Azg</string>
|
<string>xJ8ya3NBRWC24gKhcwwNeQ</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>lastKnownItemCounts</key>
|
<key>lastKnownItemCounts</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||||
<date>2020-03-27T04:00:09Z</date>
|
<date>2020-04-18T18:01:02Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||||
<date>2020-03-27T04:00:10Z</date>
|
<date>2020-04-18T17:22:55Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Binary file not shown.
@@ -5,7 +5,7 @@
|
|||||||
<key>LithiumMessageTracer</key>
|
<key>LithiumMessageTracer</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>LastReportedDate</key>
|
<key>LastReportedDate</key>
|
||||||
<date>2020-03-15T20:19:24Z</date>
|
<date>2020-04-17T17:51:16Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -11,6 +11,6 @@
|
|||||||
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
|
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>PLLastRevGeoVerFileFetchDateKey</key>
|
<key>PLLastRevGeoVerFileFetchDateKey</key>
|
||||||
<date>2020-03-27T03:59:54Z</date>
|
<date>2020-04-17T17:49:52Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>LastHistoryRowId</key>
|
<key>LastHistoryRowId</key>
|
||||||
<integer>575</integer>
|
<integer>606</integer>
|
||||||
<key>LibraryBuildTag</key>
|
<key>LibraryBuildTag</key>
|
||||||
<string>D8C4AAA1-3AB6-4A65-BEBD-99CC3E5D433E</string>
|
<string>D8C4AAA1-3AB6-4A65-BEBD-99CC3E5D433E</string>
|
||||||
<key>LibrarySchemaVersion</key>
|
<key>LibrarySchemaVersion</key>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<key>HistoricalMarker</key>
|
<key>HistoricalMarker</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>LastHistoryRowId</key>
|
<key>LastHistoryRowId</key>
|
||||||
<integer>575</integer>
|
<integer>606</integer>
|
||||||
<key>LibraryBuildTag</key>
|
<key>LibraryBuildTag</key>
|
||||||
<string>D8C4AAA1-3AB6-4A65-BEBD-99CC3E5D433E</string>
|
<string>D8C4AAA1-3AB6-4A65-BEBD-99CC3E5D433E</string>
|
||||||
<key>LibrarySchemaVersion</key>
|
<key>LibrarySchemaVersion</key>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<key>SnapshotCompletedDate</key>
|
<key>SnapshotCompletedDate</key>
|
||||||
<date>2019-07-27T13:16:43Z</date>
|
<date>2019-07-27T13:16:43Z</date>
|
||||||
<key>SnapshotLastValidated</key>
|
<key>SnapshotLastValidated</key>
|
||||||
<date>2020-03-27T04:02:59Z</date>
|
<date>2020-04-17T17:51:16Z</date>
|
||||||
<key>SnapshotTables</key>
|
<key>SnapshotTables</key>
|
||||||
<dict/>
|
<dict/>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,24 +3,24 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>BackgroundHighlightCollection</key>
|
<key>BackgroundHighlightCollection</key>
|
||||||
<date>2020-04-16T17:31:22Z</date>
|
<date>2020-04-17T14:33:32Z</date>
|
||||||
<key>BackgroundHighlightEnrichment</key>
|
<key>BackgroundHighlightEnrichment</key>
|
||||||
<date>2020-04-16T17:31:21Z</date>
|
<date>2020-04-17T14:33:32Z</date>
|
||||||
<key>BackgroundJobAssetRevGeocode</key>
|
<key>BackgroundJobAssetRevGeocode</key>
|
||||||
<date>2020-04-16T17:31:22Z</date>
|
<date>2020-04-17T14:33:33Z</date>
|
||||||
<key>BackgroundJobSearch</key>
|
<key>BackgroundJobSearch</key>
|
||||||
<date>2020-04-16T17:31:22Z</date>
|
<date>2020-04-17T14:33:33Z</date>
|
||||||
<key>BackgroundPeopleSuggestion</key>
|
<key>BackgroundPeopleSuggestion</key>
|
||||||
<date>2020-04-16T17:31:21Z</date>
|
<date>2020-04-17T14:33:31Z</date>
|
||||||
<key>BackgroundUserBehaviorProcessor</key>
|
<key>BackgroundUserBehaviorProcessor</key>
|
||||||
<date>2020-04-16T06:52:39Z</date>
|
<date>2020-04-17T07:32:04Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
|
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
|
||||||
<date>2020-04-16T17:38:41Z</date>
|
<date>2020-04-17T14:33:37Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||||
<date>2020-04-16T06:52:38Z</date>
|
<date>2020-04-17T07:32:00Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||||
<date>2020-04-16T17:31:22Z</date>
|
<date>2020-04-17T14:33:34Z</date>
|
||||||
<key>SiriPortraitDonation</key>
|
<key>SiriPortraitDonation</key>
|
||||||
<date>2020-04-16T06:52:39Z</date>
|
<date>2020-04-17T07:32:04Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,8 +3,8 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>FaceIDModelLastGenerationKey</key>
|
<key>FaceIDModelLastGenerationKey</key>
|
||||||
<date>2020-04-16T06:52:40Z</date>
|
<date>2020-04-17T07:32:07Z</date>
|
||||||
<key>LastContactClassificationKey</key>
|
<key>LastContactClassificationKey</key>
|
||||||
<date>2020-04-16T06:52:42Z</date>
|
<date>2020-04-17T07:32:12Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Binary file not shown.
@@ -4,32 +4,33 @@ from osxphotos._constants import _UNKNOWN_PERSON
|
|||||||
|
|
||||||
PHOTOS_DB = "./tests/Test-10.14.6.photoslibrary/database/photos.db"
|
PHOTOS_DB = "./tests/Test-10.14.6.photoslibrary/database/photos.db"
|
||||||
|
|
||||||
# TOP_LEVEL_FOLDERS = ["Folder1"]
|
TOP_LEVEL_FOLDERS = ["Folder1"]
|
||||||
|
|
||||||
# TOP_LEVEL_CHILDREN = ["SubFolder1", "SubFolder2"]
|
TOP_LEVEL_CHILDREN = ["SubFolder1", "SubFolder2"]
|
||||||
|
|
||||||
# FOLDER_ALBUM_DICT = {"Folder1": [], "SubFolder1": [], "SubFolder2": ["AlbumInFolder"]}
|
FOLDER_ALBUM_DICT = {"Folder1": [], "SubFolder1": [], "SubFolder2": ["AlbumInFolder"]}
|
||||||
|
|
||||||
# ALBUM_NAMES = ["Pumpkin Farm", "AlbumInFolder", "Test Album", "Test Album"]
|
ALBUM_NAMES = ["Pumpkin Farm", "AlbumInFolder", "Test Album", "Test Album (1)"]
|
||||||
ALBUM_NAMES = ["Pumpkin Farm", "Test Album", "Test Album (1)"]
|
|
||||||
|
|
||||||
# ALBUM_PARENT_DICT = {
|
ALBUM_PARENT_DICT = {
|
||||||
# "Pumpkin Farm": None,
|
"Pumpkin Farm": None,
|
||||||
# "AlbumInFolder": "SubFolder2",
|
"AlbumInFolder": "SubFolder2",
|
||||||
# "Test Album": None,
|
"Test Album": None,
|
||||||
# }
|
"Test Album (1)": None,
|
||||||
|
}
|
||||||
|
|
||||||
# ALBUM_FOLDER_NAMES_DICT = {
|
ALBUM_FOLDER_NAMES_DICT = {
|
||||||
# "Pumpkin Farm": [],
|
"Pumpkin Farm": [],
|
||||||
# "AlbumInFolder": ["Folder1", "SubFolder2"],
|
"AlbumInFolder": ["Folder1", "SubFolder2"],
|
||||||
# "Test Album": [],
|
"Test Album": [],
|
||||||
# }
|
"Test Album (1)": [],
|
||||||
|
}
|
||||||
|
|
||||||
ALBUM_LEN_DICT = {
|
ALBUM_LEN_DICT = {
|
||||||
"Pumpkin Farm": 3,
|
"Pumpkin Farm": 3,
|
||||||
"Test Album": 1,
|
"Test Album": 1,
|
||||||
"Test Album (1)": 1,
|
"Test Album (1)": 1,
|
||||||
# "AlbumInFolder": 2,
|
"AlbumInFolder": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
ALBUM_PHOTO_UUID_DICT = {
|
ALBUM_PHOTO_UUID_DICT = {
|
||||||
@@ -40,10 +41,7 @@ ALBUM_PHOTO_UUID_DICT = {
|
|||||||
],
|
],
|
||||||
"Test Album": ["8SOE9s0XQVGsuq4ONohTng"],
|
"Test Album": ["8SOE9s0XQVGsuq4ONohTng"],
|
||||||
"Test Album (1)": ["15uNd7%8RguTEgNPKHfTWw"],
|
"Test Album (1)": ["15uNd7%8RguTEgNPKHfTWw"],
|
||||||
# "AlbumInFolder": [
|
"AlbumInFolder": ["15uNd7%8RguTEgNPKHfTWw"],
|
||||||
# "3DD2C897-F19E-4CA6-8C22-B027D5A71907",
|
|
||||||
# "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
|
||||||
# ],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UUID_DICT = {"two_albums": "8SOE9s0XQVGsuq4ONohTng"}
|
UUID_DICT = {"two_albums": "8SOE9s0XQVGsuq4ONohTng"}
|
||||||
@@ -51,62 +49,57 @@ UUID_DICT = {"two_albums": "8SOE9s0XQVGsuq4ONohTng"}
|
|||||||
######### Test FolderInfo ##########
|
######### Test FolderInfo ##########
|
||||||
|
|
||||||
|
|
||||||
def test_folders_1(caplog):
|
def test_folders_1():
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
|
||||||
folders = photosdb.folders
|
folders = photosdb.folders
|
||||||
assert folders == []
|
|
||||||
assert "Folders not yet implemented for this DB version" in caplog.text
|
|
||||||
|
|
||||||
# # top level folders
|
# top level folders
|
||||||
# folders = photosdb.folders
|
folders = photosdb.folder_info
|
||||||
# assert len(folders) == 1
|
assert len(folders) == 1
|
||||||
|
|
||||||
# # check folder names
|
# check folder names
|
||||||
# folder_names = [f.title for f in folders]
|
folder_names = [f.title for f in folders]
|
||||||
# assert sorted(folder_names) == sorted(TOP_LEVEL_FOLDERS)
|
assert sorted(folder_names) == sorted(TOP_LEVEL_FOLDERS)
|
||||||
|
|
||||||
|
|
||||||
def test_folder_names(caplog):
|
def test_folder_names():
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
|
||||||
# check folder names
|
# check folder names
|
||||||
folder_names = photosdb.folders
|
folder_names = photosdb.folders
|
||||||
assert folder_names == []
|
assert folder_names == TOP_LEVEL_FOLDERS
|
||||||
assert "Folders not yet implemented for this DB version" in caplog.text
|
assert sorted(folder_names) == sorted(TOP_LEVEL_FOLDERS)
|
||||||
# assert sorted(folder_names) == sorted(TOP_LEVEL_FOLDERS)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Folders not yet impleted in Photos < 5")
|
|
||||||
def test_folders_len():
|
def test_folders_len():
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
|
||||||
# top level folders
|
# top level folders
|
||||||
folders = photosdb.folders
|
folders = photosdb.folder_info
|
||||||
assert len(folders[0]) == len(TOP_LEVEL_CHILDREN)
|
assert len(folders[0]) == len(TOP_LEVEL_CHILDREN)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Folders not yet impleted in Photos < 5")
|
|
||||||
def test_folders_children():
|
def test_folders_children():
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
|
||||||
# top level folders
|
# top level folders
|
||||||
folders = photosdb.folders
|
folders = photosdb.folder_info
|
||||||
|
|
||||||
# children of top level folder
|
# children of top level folder
|
||||||
children = folders[0].folders
|
children = folders[0].subfolders
|
||||||
children_names = [f.title for f in children]
|
children_names = [f.title for f in children]
|
||||||
assert sorted(children_names) == sorted(TOP_LEVEL_CHILDREN)
|
assert sorted(children_names) == sorted(TOP_LEVEL_CHILDREN)
|
||||||
|
|
||||||
for child in folders[0].folders:
|
for child in folders[0].subfolders:
|
||||||
# check valid children FolderInfo
|
# check valid children FolderInfo
|
||||||
assert child.parent
|
assert child.parent
|
||||||
assert child.parent.uuid == folders[0].uuid
|
assert child.parent.uuid == folders[0].uuid
|
||||||
@@ -116,38 +109,36 @@ def test_folders_children():
|
|||||||
assert sorted(folder_names) == sorted(TOP_LEVEL_FOLDERS)
|
assert sorted(folder_names) == sorted(TOP_LEVEL_FOLDERS)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Folders not yet impleted in Photos < 5")
|
|
||||||
def test_folders_parent():
|
def test_folders_parent():
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
|
||||||
# top level folders
|
# top level folders
|
||||||
folders = photosdb.folders
|
folders = photosdb.folder_info
|
||||||
|
|
||||||
# parent of top level folder should be none
|
# parent of top level folder should be none
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
assert folder.parent is None
|
assert folder.parent is None
|
||||||
for child in folder.folders:
|
for child in folder.subfolders:
|
||||||
# children's parent uuid should match folder uuid
|
# children's parent uuid should match folder uuid
|
||||||
assert child.parent
|
assert child.parent
|
||||||
assert child.parent.uuid == folder.uuid
|
assert child.parent.uuid == folder.uuid
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Folders not yet impleted in Photos < 5")
|
|
||||||
def test_folders_albums():
|
def test_folders_albums():
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
|
||||||
# top level folders
|
# top level folders
|
||||||
folders = photosdb.folders
|
folders = photosdb.folder_info
|
||||||
|
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
name = folder.title
|
name = folder.title
|
||||||
albums = [a.title for a in folder.album_info]
|
albums = [a.title for a in folder.album_info]
|
||||||
assert sorted(albums) == sorted(FOLDER_ALBUM_DICT[name])
|
assert sorted(albums) == sorted(FOLDER_ALBUM_DICT[name])
|
||||||
for child in folder.folders:
|
for child in folder.subfolders:
|
||||||
name = child.title
|
name = child.title
|
||||||
albums = [a.title for a in child.album_info]
|
albums = [a.title for a in child.album_info]
|
||||||
assert sorted(albums) == sorted(FOLDER_ALBUM_DICT[name])
|
assert sorted(albums) == sorted(FOLDER_ALBUM_DICT[name])
|
||||||
@@ -162,14 +153,14 @@ def test_albums_1():
|
|||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
|
||||||
albums = photosdb.album_info
|
albums = photosdb.album_info
|
||||||
assert len(albums) == 3
|
assert len(albums) == 4
|
||||||
|
|
||||||
# check names
|
# check names
|
||||||
album_names = [a.title for a in albums]
|
album_names = [a.title for a in albums]
|
||||||
assert sorted(album_names) == sorted(ALBUM_NAMES)
|
assert sorted(album_names) == sorted(ALBUM_NAMES)
|
||||||
|
|
||||||
|
|
||||||
def test_albums_parent(caplog):
|
def test_albums_parent():
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
@@ -178,11 +169,10 @@ def test_albums_parent(caplog):
|
|||||||
|
|
||||||
for album in albums:
|
for album in albums:
|
||||||
parent = album.parent.title if album.parent else None
|
parent = album.parent.title if album.parent else None
|
||||||
assert "Folders not yet implemented for this DB version" in caplog.text
|
assert parent == ALBUM_PARENT_DICT[album.title]
|
||||||
# assert parent == ALBUM_PARENT_DICT[album.title]
|
|
||||||
|
|
||||||
|
|
||||||
def test_albums_folder_names(caplog):
|
def test_albums_folder_names():
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
@@ -191,11 +181,10 @@ def test_albums_folder_names(caplog):
|
|||||||
|
|
||||||
for album in albums:
|
for album in albums:
|
||||||
folder_names = album.folder_names
|
folder_names = album.folder_names
|
||||||
assert "Folders not yet implemented for this DB version" in caplog.text
|
assert folder_names == ALBUM_FOLDER_NAMES_DICT[album.title]
|
||||||
# assert folder_names == ALBUM_FOLDER_NAMES_DICT[album.title]
|
|
||||||
|
|
||||||
|
|
||||||
def test_albums_folders(caplog):
|
def test_albums_folders():
|
||||||
import osxphotos
|
import osxphotos
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
@@ -204,9 +193,8 @@ def test_albums_folders(caplog):
|
|||||||
|
|
||||||
for album in albums:
|
for album in albums:
|
||||||
folders = album.folder_list
|
folders = album.folder_list
|
||||||
assert "Folders not yet implemented for this DB version" in caplog.text
|
folder_names = [f.title for f in folders]
|
||||||
# folder_names = [f.title for f in folders]
|
assert folder_names == ALBUM_FOLDER_NAMES_DICT[album.title]
|
||||||
# assert folder_names == ALBUM_FOLDER_NAMES_DICT[album.title]
|
|
||||||
|
|
||||||
|
|
||||||
def test_albums_len():
|
def test_albums_len():
|
||||||
|
|||||||
@@ -780,7 +780,7 @@ def test_no_folder_2_15():
|
|||||||
assert item["albums"] == ["AlbumInFolder"]
|
assert item["albums"] == ["AlbumInFolder"]
|
||||||
|
|
||||||
|
|
||||||
def test_no_folder_1_14(caplog):
|
def test_no_folder_1_14():
|
||||||
# test --folder on 10.14
|
# test --folder on 10.14
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -797,6 +797,5 @@ def test_no_folder_1_14(caplog):
|
|||||||
)
|
)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
json_got = json.loads(result.output)
|
json_got = json.loads(result.output)
|
||||||
|
assert len(json_got) == 1 # single element
|
||||||
assert len(json_got) == 0 # single element
|
assert json_got[0]["uuid"] == "15uNd7%8RguTEgNPKHfTWw"
|
||||||
assert "not yet implemented" in caplog.text
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ KEYWORDS = [
|
|||||||
"United Kingdom",
|
"United Kingdom",
|
||||||
]
|
]
|
||||||
PERSONS = ["Katie", "Suzy", "Maria"]
|
PERSONS = ["Katie", "Suzy", "Maria"]
|
||||||
ALBUMS = ["Pumpkin Farm", "Test Album", "Test Album (1)"]
|
ALBUMS = ["Pumpkin Farm", "AlbumInFolder", "Test Album", "Test Album (1)"]
|
||||||
KEYWORDS_DICT = {
|
KEYWORDS_DICT = {
|
||||||
"Kids": 4,
|
"Kids": 4,
|
||||||
"wedding": 2,
|
"wedding": 2,
|
||||||
@@ -31,7 +31,12 @@ 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, "Test Album": 1, "Test Album (1)": 1}
|
ALBUM_DICT = {
|
||||||
|
"Pumpkin Farm": 3,
|
||||||
|
"AlbumInFolder": 1,
|
||||||
|
"Test Album": 1,
|
||||||
|
"Test Album (1)": 1,
|
||||||
|
}
|
||||||
|
|
||||||
UUID_DICT = {
|
UUID_DICT = {
|
||||||
"favorite": "6bxcNnzRQKGnK4uPrCJ9UQ",
|
"favorite": "6bxcNnzRQKGnK4uPrCJ9UQ",
|
||||||
@@ -131,7 +136,9 @@ def test_attributes():
|
|||||||
)
|
)
|
||||||
assert p.description == "Girl holding pumpkin"
|
assert p.description == "Girl holding pumpkin"
|
||||||
assert p.title == "I found one!"
|
assert p.title == "I found one!"
|
||||||
assert p.albums == ["Pumpkin Farm", "Test Album (1)"]
|
assert sorted(p.albums) == sorted(
|
||||||
|
["Pumpkin Farm", "AlbumInFolder", "Test Album (1)"]
|
||||||
|
)
|
||||||
assert p.persons == ["Katie"]
|
assert p.persons == ["Katie"]
|
||||||
assert p.path.endswith(
|
assert p.path.endswith(
|
||||||
"/tests/Test-10.14.6.photoslibrary/Masters/2019/07/27/20190727-131650/Pumkins2.jpg"
|
"/tests/Test-10.14.6.photoslibrary/Masters/2019/07/27/20190727-131650/Pumkins2.jpg"
|
||||||
|
|||||||
@@ -7,8 +7,13 @@ PHOTOS_DB = "./tests/Test-10.14.6.photoslibrary/database/photos.db"
|
|||||||
PHOTOS_DB_PATH = "/Test-10.14.6.photoslibrary/database/photos.db"
|
PHOTOS_DB_PATH = "/Test-10.14.6.photoslibrary/database/photos.db"
|
||||||
PHOTOS_LIBRARY_PATH = "/Test-10.14.6.photoslibrary"
|
PHOTOS_LIBRARY_PATH = "/Test-10.14.6.photoslibrary"
|
||||||
|
|
||||||
ALBUMS = ["Pumpkin Farm", "Test Album", "Test Album (1)"]
|
ALBUMS = ["Pumpkin Farm", "AlbumInFolder", "Test Album", "Test Album (1)"]
|
||||||
ALBUM_DICT = {"Pumpkin Farm": 3, "Test Album": 1, "Test Album (1)": 1}
|
ALBUM_DICT = {
|
||||||
|
"Pumpkin Farm": 3,
|
||||||
|
"AlbumInFolder": 1,
|
||||||
|
"Test Album": 1,
|
||||||
|
"Test Album (1)": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_album_names():
|
def test_album_names():
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ UUID_DICT = {
|
|||||||
"0_2_0": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
|
"0_2_0": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
|
||||||
"folder_album_1": "3DD2C897-F19E-4CA6-8C22-B027D5A71907",
|
"folder_album_1": "3DD2C897-F19E-4CA6-8C22-B027D5A71907",
|
||||||
"folder_album_no_folder": "D79B8D77-BFFC-460B-9312-034F2877D35B",
|
"folder_album_no_folder": "D79B8D77-BFFC-460B-9312-034F2877D35B",
|
||||||
"mojave_no_folder": "15uNd7%8RguTEgNPKHfTWw",
|
"mojave_album_1": "15uNd7%8RguTEgNPKHfTWw",
|
||||||
}
|
}
|
||||||
|
|
||||||
TEMPLATE_VALUES = {
|
TEMPLATE_VALUES = {
|
||||||
@@ -341,17 +341,16 @@ def test_subst_multi_folder_albums_2():
|
|||||||
|
|
||||||
|
|
||||||
def test_subst_multi_folder_albums_3(caplog):
|
def test_subst_multi_folder_albums_3(caplog):
|
||||||
""" Test substitutions for folder_album on < Photos 5 (not implemented) """
|
""" Test substitutions for folder_album on < Photos 5 """
|
||||||
import osxphotos
|
import osxphotos
|
||||||
from osxphotos.template import render_filepath_template
|
from osxphotos.template import render_filepath_template
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB_14_6)
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB_14_6)
|
||||||
|
|
||||||
# photo in an album in a folder
|
# photo in an album in a folder
|
||||||
photo = photosdb.photos(uuid=[UUID_DICT["mojave_no_folder"]])[0]
|
photo = photosdb.photos(uuid=[UUID_DICT["mojave_album_1"]])[0]
|
||||||
template = "{folder_album}"
|
template = "{folder_album}"
|
||||||
expected = ["Pumpkin Farm", "Test Album (1)"]
|
expected = ["Folder1/SubFolder2/AlbumInFolder", "Pumpkin Farm", "Test Album (1)"]
|
||||||
rendered, unknown = render_filepath_template(template, photo)
|
rendered, unknown = render_filepath_template(template, photo)
|
||||||
assert sorted(rendered) == sorted(expected)
|
assert sorted(rendered) == sorted(expected)
|
||||||
assert unknown == []
|
assert unknown == []
|
||||||
assert "not yet implemented" in caplog.text
|
|
||||||
|
|||||||
Reference in New Issue
Block a user