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).
|
||||
|
||||
#### [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)
|
||||
|
||||
> 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)
|
||||
- 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)
|
||||
|
||||
> 13 April 2020
|
||||
|
||||
#### [v0.15.0](https://github.com/RhetTbull/osxphotos/compare/v0.14.21...v0.15.0)
|
||||
#### [v0.15.1](https://github.com/RhetTbull/osxphotos/compare/v0.14.21...v0.15.1)
|
||||
|
||||
> 14 December 2019
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ _TESTED_DB_VERSIONS = ["6000", "4025", "4016", "3301", "2622"]
|
||||
_PHOTOS_3_VERSION = "3301"
|
||||
|
||||
# versions 5.0 and later have a different database structure
|
||||
_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_4_VERSION = "4025" # latest Mojove version on 10.14.6
|
||||
_PHOTOS_5_VERSION = "6000" # seems to be current on 10.15.1 through 10.15.4
|
||||
|
||||
# which major version operating systems have been tested
|
||||
_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_FOLDER_KIND = 4000 # user 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__ = "0.28.1"
|
||||
__version__ = "0.28.2"
|
||||
|
||||
@@ -12,7 +12,13 @@ PhotosDB.folders() returns a list of FolderInfo objects
|
||||
|
||||
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:
|
||||
@@ -53,14 +59,13 @@ class AlbumInfo:
|
||||
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
||||
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:
|
||||
return self._folder_names
|
||||
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
|
||||
|
||||
@property
|
||||
@@ -70,10 +75,6 @@ class AlbumInfo:
|
||||
["Top level folder", "sub folder 1", "sub folder 2", ...]
|
||||
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:
|
||||
return self._folders
|
||||
except AttributeError:
|
||||
@@ -83,19 +84,23 @@ class AlbumInfo:
|
||||
@property
|
||||
def parent(self):
|
||||
""" 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:
|
||||
return self._parent
|
||||
except AttributeError:
|
||||
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
|
||||
)
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
parent_uuid = self._db._dbalbum_details[self._uuid]["folderUuid"]
|
||||
self._parent = (
|
||||
FolderInfo(db=self._db, uuid=parent_uuid)
|
||||
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
|
||||
|
||||
def __len__(self):
|
||||
@@ -112,8 +117,12 @@ class FolderInfo:
|
||||
def __init__(self, db=None, uuid=None):
|
||||
self._uuid = uuid
|
||||
self._db = db
|
||||
self._pk = self._db._dbalbum_details[uuid]["pk"]
|
||||
self._title = self._db._dbalbum_details[uuid]["title"]
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
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
|
||||
def title(self):
|
||||
@@ -131,13 +140,22 @@ class FolderInfo:
|
||||
try:
|
||||
return self._albums
|
||||
except AttributeError:
|
||||
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
|
||||
]
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
albums = [
|
||||
AlbumInfo(db=self._db, uuid=album)
|
||||
for album, detail in self._db._dbalbum_details.items()
|
||||
if not detail["intrash"]
|
||||
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
|
||||
return self._albums
|
||||
|
||||
@@ -147,12 +165,20 @@ class FolderInfo:
|
||||
try:
|
||||
return self._parent
|
||||
except AttributeError:
|
||||
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
|
||||
)
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
parent_uuid = self._db._dbfolder_details[self._uuid]["parentFolderUuid"]
|
||||
self._parent = (
|
||||
FolderInfo(db=self._db, uuid=parent_uuid)
|
||||
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
|
||||
|
||||
@property
|
||||
@@ -161,13 +187,22 @@ class FolderInfo:
|
||||
try:
|
||||
return self._folders
|
||||
except AttributeError:
|
||||
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
|
||||
]
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
folders = [
|
||||
FolderInfo(db=self._db, uuid=folder)
|
||||
for folder, detail in self._db._dbfolder_details.items()
|
||||
if not detail["intrash"]
|
||||
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
|
||||
return self._folders
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ from mako.template import Template
|
||||
from ._constants import (
|
||||
_MOVIE_TYPE,
|
||||
_PHOTO_TYPE,
|
||||
_PHOTOS_4_VERSION,
|
||||
_PHOTOS_5_SHARED_PHOTO_PATH,
|
||||
_PHOTOS_5_VERSION,
|
||||
_TEMPLATE_DIR,
|
||||
_XMP_TEMPLATE_NAME,
|
||||
)
|
||||
@@ -98,7 +98,7 @@ class PhotoInfo:
|
||||
if self._info["isMissing"] == 1:
|
||||
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"]
|
||||
if vol is not None:
|
||||
photopath = os.path.join("/Volumes", vol, self._info["imagePath"])
|
||||
@@ -137,7 +137,7 @@ class PhotoInfo:
|
||||
|
||||
photopath = None
|
||||
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
if self._info["hasAdjustments"]:
|
||||
edit_id = self._info["edit_resource_id"]
|
||||
if edit_id is not None:
|
||||
@@ -269,7 +269,7 @@ class PhotoInfo:
|
||||
# )
|
||||
# return photopath
|
||||
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
vol = self._info["raw_info"]["volume"]
|
||||
if vol is not None:
|
||||
photopath = os.path.join(
|
||||
@@ -400,7 +400,7 @@ class PhotoInfo:
|
||||
def shared(self):
|
||||
""" returns True if photos is in a shared iCloud album otherwise false
|
||||
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"]
|
||||
else:
|
||||
return None
|
||||
@@ -445,7 +445,7 @@ class PhotoInfo:
|
||||
""" Returns True if photo is a cloud asset (in an iCloud library),
|
||||
otherwise False
|
||||
"""
|
||||
if self._db._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db._db_version <= _PHOTOS_4_VERSION:
|
||||
return (
|
||||
True
|
||||
if self._info["cloudLibraryState"] is not None
|
||||
@@ -488,7 +488,7 @@ class PhotoInfo:
|
||||
If photo is missing, returns 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:
|
||||
live_model_id = self._info["live_model_id"]
|
||||
if live_model_id == None:
|
||||
@@ -579,7 +579,7 @@ class PhotoInfo:
|
||||
# implementation note: doesn't create the PlaceInfo object until requested
|
||||
# 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:
|
||||
return self._place # pylint: disable=access-member-before-definition
|
||||
except AttributeError:
|
||||
@@ -717,8 +717,11 @@ class PhotoInfo:
|
||||
# warn if suffixes don't match but ignore .JPG / .jpeg as
|
||||
# Photo's often converts .JPG to .jpeg
|
||||
suffixes = sorted([x.lower() for x in [dest.suffix, actual_suffix]])
|
||||
if dest.suffix != actual_suffix and suffixes != [".jpeg", ".jpg"]:
|
||||
logging.debug(
|
||||
if dest.suffix.lower() != actual_suffix.lower() and suffixes != [
|
||||
".jpeg",
|
||||
".jpg",
|
||||
]:
|
||||
logging.warning(
|
||||
f"Invalid destination suffix: {dest.suffix}, should be {actual_suffix}"
|
||||
)
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ from ._constants import (
|
||||
_TESTED_DB_VERSIONS,
|
||||
_TESTED_OS_VERSIONS,
|
||||
_UNKNOWN_PERSON,
|
||||
_PHOTOS_4_ALBUM_KIND,
|
||||
_PHOTOS_4_TOP_LEVEL_ALBUM,
|
||||
_PHOTOS_5_ROOT_FOLDER_KIND,
|
||||
_PHOTOS_5_FOLDER_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}}, }
|
||||
self._dbalbum_folders = {}
|
||||
|
||||
# Dict with information about folders
|
||||
self._dbfolder_details = {}
|
||||
|
||||
# Will hold the primary key of root folder
|
||||
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 """
|
||||
|
||||
# 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(
|
||||
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
|
||||
)
|
||||
@@ -348,33 +353,43 @@ class PhotosDB:
|
||||
@property
|
||||
def folder_info(self):
|
||||
""" return list FolderInfo objects representing top-level folders in the photos database """
|
||||
if self._db_version < _PHOTOS_5_VERSION:
|
||||
logging.warning("Folders not yet implemented for this DB version")
|
||||
return []
|
||||
|
||||
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
|
||||
]
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
folders = [
|
||||
FolderInfo(db=self, uuid=folder)
|
||||
for folder, detail in self._dbfolder_details.items()
|
||||
if not detail["intrash"]
|
||||
and not detail["isMagic"]
|
||||
and detail["parentFolderUuid"] == _PHOTOS_4_TOP_LEVEL_ALBUM
|
||||
]
|
||||
else:
|
||||
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
|
||||
|
||||
@property
|
||||
def folders(self):
|
||||
""" return list of top-level folder names in the photos database """
|
||||
if self._db_version < _PHOTOS_5_VERSION:
|
||||
logging.warning("Folders not yet implemented for this DB version")
|
||||
return []
|
||||
|
||||
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
|
||||
]
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
folder_names = [
|
||||
folder["name"]
|
||||
for folder in self._dbfolder_details.values()
|
||||
if not folder["intrash"]
|
||||
and not folder["isMagic"]
|
||||
and folder["parentFolderUuid"] == _PHOTOS_4_TOP_LEVEL_ALBUM
|
||||
]
|
||||
else:
|
||||
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
|
||||
|
||||
@property
|
||||
@@ -395,7 +410,7 @@ class PhotosDB:
|
||||
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 self._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
logging.warning(
|
||||
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 self._db_version < _PHOTOS_5_VERSION:
|
||||
if self._db_version <= _PHOTOS_4_VERSION:
|
||||
logging.warning(
|
||||
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
|
||||
c.execute(
|
||||
"select RKPerson.name, RKVersion.uuid from RKFace, RKPerson, RKVersion, RKMaster "
|
||||
+ "where RKFace.personID = RKperson.modelID and RKVersion.modelId = RKFace.ImageModelId "
|
||||
+ "and RKVersion.masterUuid = RKMaster.uuid and "
|
||||
+ "RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
||||
""" select RKPerson.name, RKVersion.uuid from RKFace, RKPerson, RKVersion, RKMaster
|
||||
where RKFace.personID = RKperson.modelID and RKVersion.modelId = RKFace.ImageModelId
|
||||
and RKVersion.masterUuid = RKMaster.uuid
|
||||
and RKVersion.isInTrash = 0 """
|
||||
)
|
||||
for person in c:
|
||||
if person[0] is None:
|
||||
@@ -538,10 +553,13 @@ class PhotosDB:
|
||||
|
||||
# Get info on albums
|
||||
c.execute(
|
||||
"select RKAlbum.uuid, RKVersion.uuid from RKAlbum, RKVersion, RKAlbumVersion "
|
||||
+ "where RKAlbum.modelID = RKAlbumVersion.albumId and "
|
||||
+ "RKAlbumVersion.versionID = RKVersion.modelId and "
|
||||
+ "RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
||||
""" select
|
||||
RKAlbum.uuid,
|
||||
RKVersion.uuid
|
||||
from RKAlbum, RKVersion, RKAlbumVersion
|
||||
where RKAlbum.modelID = RKAlbumVersion.albumId and
|
||||
RKAlbumVersion.versionID = RKVersion.modelId
|
||||
and RKVersion.isInTrash = 0 """
|
||||
)
|
||||
for album in c:
|
||||
# store by uuid in _dbalbums_uuid and by album in _dbalbums_album
|
||||
@@ -554,17 +572,31 @@ class PhotosDB:
|
||||
|
||||
# now get additional details about albums
|
||||
c.execute(
|
||||
"SELECT "
|
||||
"uuid, " # 0
|
||||
"name, " # 1
|
||||
"cloudLibraryState, " # 2
|
||||
"cloudIdentifier, " # 3
|
||||
"isInTrash " # 4
|
||||
"FROM RKAlbum "
|
||||
""" SELECT
|
||||
uuid,
|
||||
name,
|
||||
cloudLibraryState,
|
||||
cloudIdentifier,
|
||||
isInTrash,
|
||||
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:
|
||||
self._dbalbum_details[album[0]] = {
|
||||
"_uuid": album[0],
|
||||
"title": album[1],
|
||||
"cloudlibrarystate": album[2],
|
||||
"cloudidentifier": album[3],
|
||||
@@ -573,22 +605,80 @@ class PhotosDB:
|
||||
"cloudownerfirstname": None, # Photos 5
|
||||
"cloudownderlastname": 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():
|
||||
logging.debug(f"Finished walking through albums")
|
||||
logging.debug(pformat(self._dbalbums_album))
|
||||
logging.debug(pformat(self._dbalbums_uuid))
|
||||
logging.debug(pformat(self._dbalbum_details))
|
||||
logging.debug(pformat(self._dbalbum_folders))
|
||||
logging.debug(pformat(self._dbfolder_details))
|
||||
|
||||
# Get info on keywords
|
||||
c.execute(
|
||||
"select RKKeyword.name, RKVersion.uuid, RKMaster.uuid from "
|
||||
+ "RKKeyword, RKKeywordForVersion, RKVersion, RKMaster "
|
||||
+ "where RKKeyword.modelId = RKKeyWordForVersion.keywordID and "
|
||||
+ "RKVersion.modelID = RKKeywordForVersion.versionID "
|
||||
+ "and RKMaster.uuid = RKVersion.masterUuid "
|
||||
+ "and RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
||||
""" select RKKeyword.name, RKVersion.uuid, RKMaster.uuid from
|
||||
RKKeyword, RKKeywordForVersion, RKVersion, RKMaster
|
||||
where RKKeyword.modelId = RKKeyWordForVersion.keywordID and
|
||||
RKVersion.modelID = RKKeywordForVersion.versionID and
|
||||
RKMaster.uuid = RKVersion.masterUuid and
|
||||
RKVersion.isInTrash = 0 """
|
||||
)
|
||||
for keyword in c:
|
||||
if not keyword[1] in self._dbkeywords_uuid:
|
||||
@@ -899,13 +989,13 @@ class PhotosDB:
|
||||
|
||||
# get details on external edits
|
||||
c.execute(
|
||||
"SELECT RKVersion.uuid, "
|
||||
"RKVersion.adjustmentUuid, "
|
||||
"RKAdjustmentData.originator, "
|
||||
"RKAdjustmentData.format "
|
||||
"FROM RKVersion, RKAdjustmentData "
|
||||
"WHERE RKVersion.adjustmentUuid = RKAdjustmentData.uuid "
|
||||
"AND RKVersion.isInTrash = 0"
|
||||
""" SELECT RKVersion.uuid,
|
||||
RKVersion.adjustmentUuid,
|
||||
RKAdjustmentData.originator,
|
||||
RKAdjustmentData.format
|
||||
FROM RKVersion, RKAdjustmentData
|
||||
WHERE RKVersion.adjustmentUuid = RKAdjustmentData.uuid
|
||||
AND RKVersion.isInTrash = 0 """
|
||||
)
|
||||
|
||||
for row in c:
|
||||
@@ -1122,6 +1212,30 @@ class PhotosDB:
|
||||
logging.debug("Burst Photos (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):
|
||||
""" process the Photos database to extract info """
|
||||
""" works on Photos version >= 5.0 """
|
||||
@@ -1228,6 +1342,8 @@ class PhotosDB:
|
||||
self._folder_root_pk = self._dbalbum_details[root_uuid[0]]["pk"]
|
||||
|
||||
# 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():
|
||||
pk_parent = details["parentfolder"]
|
||||
if pk_parent is None:
|
||||
@@ -1246,10 +1362,10 @@ class PhotosDB:
|
||||
for album, details in self._dbalbum_details.items():
|
||||
# if details["kind"] in [_PHOTOS_5_ALBUM_KIND, _PHOTOS_5_FOLDER_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
|
||||
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] = []
|
||||
|
||||
if _debug():
|
||||
@@ -1257,6 +1373,7 @@ class PhotosDB:
|
||||
logging.debug(pformat(self._dbalbums_album))
|
||||
logging.debug(pformat(self._dbalbums_uuid))
|
||||
logging.debug(pformat(self._dbalbum_details))
|
||||
logging.debug(pformat(self._dbalbum_folders))
|
||||
|
||||
# get details on keywords
|
||||
c.execute(
|
||||
@@ -1747,14 +1864,11 @@ class PhotosDB:
|
||||
logging.debug("Burst Photos (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
|
||||
uuid: uuid of the album/folder being processed
|
||||
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
|
||||
parent = self._dbalbum_details[uuid]["parentfolder"]
|
||||
|
||||
@@ -1770,10 +1884,50 @@ class PhotosDB:
|
||||
|
||||
# recurse to keep building
|
||||
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
|
||||
|
||||
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
|
||||
the folder list is in form:
|
||||
["Top level folder", "sub folder 1", "sub folder 2"]
|
||||
@@ -1808,6 +1962,46 @@ class PhotosDB:
|
||||
return hierarchy
|
||||
|
||||
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
|
||||
["Top level folder", "sub folder 1", "sub folder 2"]
|
||||
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>
|
||||
<array>
|
||||
<string>TopLevelAlbums</string>
|
||||
<string>QtSnVvTkQ%i2z3hB834M1A</string>
|
||||
<string>TopLevelSlideshows</string>
|
||||
<string>N7eQ4VhfTfeHFp9PPHaJDw</string>
|
||||
</array>
|
||||
<key>IPXWorkspaceControllerZoomLevelsKey</key>
|
||||
<dict>
|
||||
<key>kZoomLevelIdentifierAlbums</key>
|
||||
<integer>10</integer>
|
||||
<integer>5</integer>
|
||||
<key>kZoomLevelIdentifierVersions</key>
|
||||
<integer>7</integer>
|
||||
</dict>
|
||||
@@ -23,11 +25,11 @@
|
||||
<key>key</key>
|
||||
<integer>1</integer>
|
||||
<key>lastKnownDisplayName</key>
|
||||
<string>Test Album (1)</string>
|
||||
<string>Pumpkin Farm (1)</string>
|
||||
<key>type</key>
|
||||
<string>album</string>
|
||||
<key>uuid</key>
|
||||
<string>Uq6qsKihRRSjMHTiD+0Azg</string>
|
||||
<string>xJ8ya3NBRWC24gKhcwwNeQ</string>
|
||||
</dict>
|
||||
<key>lastKnownItemCounts</key>
|
||||
<dict>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||
<date>2020-03-27T04:00:09Z</date>
|
||||
<date>2020-04-18T18:01:02Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||
<date>2020-03-27T04:00:10Z</date>
|
||||
<date>2020-04-18T17:22:55Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
Binary file not shown.
@@ -5,7 +5,7 @@
|
||||
<key>LithiumMessageTracer</key>
|
||||
<dict>
|
||||
<key>LastReportedDate</key>
|
||||
<date>2020-03-15T20:19:24Z</date>
|
||||
<date>2020-04-17T17:51:16Z</date>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
|
||||
<integer>1</integer>
|
||||
<key>PLLastRevGeoVerFileFetchDateKey</key>
|
||||
<date>2020-03-27T03:59:54Z</date>
|
||||
<date>2020-04-17T17:49:52Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>LastHistoryRowId</key>
|
||||
<integer>575</integer>
|
||||
<integer>606</integer>
|
||||
<key>LibraryBuildTag</key>
|
||||
<string>D8C4AAA1-3AB6-4A65-BEBD-99CC3E5D433E</string>
|
||||
<key>LibrarySchemaVersion</key>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>HistoricalMarker</key>
|
||||
<dict>
|
||||
<key>LastHistoryRowId</key>
|
||||
<integer>575</integer>
|
||||
<integer>606</integer>
|
||||
<key>LibraryBuildTag</key>
|
||||
<string>D8C4AAA1-3AB6-4A65-BEBD-99CC3E5D433E</string>
|
||||
<key>LibrarySchemaVersion</key>
|
||||
@@ -24,7 +24,7 @@
|
||||
<key>SnapshotCompletedDate</key>
|
||||
<date>2019-07-27T13:16:43Z</date>
|
||||
<key>SnapshotLastValidated</key>
|
||||
<date>2020-03-27T04:02:59Z</date>
|
||||
<date>2020-04-17T17:51:16Z</date>
|
||||
<key>SnapshotTables</key>
|
||||
<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">
|
||||
<dict>
|
||||
<key>BackgroundHighlightCollection</key>
|
||||
<date>2020-04-16T17:31:22Z</date>
|
||||
<date>2020-04-17T14:33:32Z</date>
|
||||
<key>BackgroundHighlightEnrichment</key>
|
||||
<date>2020-04-16T17:31:21Z</date>
|
||||
<date>2020-04-17T14:33:32Z</date>
|
||||
<key>BackgroundJobAssetRevGeocode</key>
|
||||
<date>2020-04-16T17:31:22Z</date>
|
||||
<date>2020-04-17T14:33:33Z</date>
|
||||
<key>BackgroundJobSearch</key>
|
||||
<date>2020-04-16T17:31:22Z</date>
|
||||
<date>2020-04-17T14:33:33Z</date>
|
||||
<key>BackgroundPeopleSuggestion</key>
|
||||
<date>2020-04-16T17:31:21Z</date>
|
||||
<date>2020-04-17T14:33:31Z</date>
|
||||
<key>BackgroundUserBehaviorProcessor</key>
|
||||
<date>2020-04-16T06:52:39Z</date>
|
||||
<date>2020-04-17T07:32:04Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
|
||||
<date>2020-04-16T17:38:41Z</date>
|
||||
<date>2020-04-17T14:33:37Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||
<date>2020-04-16T06:52:38Z</date>
|
||||
<date>2020-04-17T07:32:00Z</date>
|
||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||
<date>2020-04-16T17:31:22Z</date>
|
||||
<date>2020-04-17T14:33:34Z</date>
|
||||
<key>SiriPortraitDonation</key>
|
||||
<date>2020-04-16T06:52:39Z</date>
|
||||
<date>2020-04-17T07:32:04Z</date>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,8 +3,8 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>FaceIDModelLastGenerationKey</key>
|
||||
<date>2020-04-16T06:52:40Z</date>
|
||||
<date>2020-04-17T07:32:07Z</date>
|
||||
<key>LastContactClassificationKey</key>
|
||||
<date>2020-04-16T06:52:42Z</date>
|
||||
<date>2020-04-17T07:32:12Z</date>
|
||||
</dict>
|
||||
</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"
|
||||
|
||||
# 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", "Test Album", "Test Album (1)"]
|
||||
ALBUM_NAMES = ["Pumpkin Farm", "AlbumInFolder", "Test Album", "Test Album (1)"]
|
||||
|
||||
# ALBUM_PARENT_DICT = {
|
||||
# "Pumpkin Farm": None,
|
||||
# "AlbumInFolder": "SubFolder2",
|
||||
# "Test Album": None,
|
||||
# }
|
||||
ALBUM_PARENT_DICT = {
|
||||
"Pumpkin Farm": None,
|
||||
"AlbumInFolder": "SubFolder2",
|
||||
"Test Album": None,
|
||||
"Test Album (1)": None,
|
||||
}
|
||||
|
||||
# ALBUM_FOLDER_NAMES_DICT = {
|
||||
# "Pumpkin Farm": [],
|
||||
# "AlbumInFolder": ["Folder1", "SubFolder2"],
|
||||
# "Test Album": [],
|
||||
# }
|
||||
ALBUM_FOLDER_NAMES_DICT = {
|
||||
"Pumpkin Farm": [],
|
||||
"AlbumInFolder": ["Folder1", "SubFolder2"],
|
||||
"Test Album": [],
|
||||
"Test Album (1)": [],
|
||||
}
|
||||
|
||||
ALBUM_LEN_DICT = {
|
||||
"Pumpkin Farm": 3,
|
||||
"Test Album": 1,
|
||||
"Test Album (1)": 1,
|
||||
# "AlbumInFolder": 2,
|
||||
"AlbumInFolder": 1,
|
||||
}
|
||||
|
||||
ALBUM_PHOTO_UUID_DICT = {
|
||||
@@ -40,10 +41,7 @@ ALBUM_PHOTO_UUID_DICT = {
|
||||
],
|
||||
"Test Album": ["8SOE9s0XQVGsuq4ONohTng"],
|
||||
"Test Album (1)": ["15uNd7%8RguTEgNPKHfTWw"],
|
||||
# "AlbumInFolder": [
|
||||
# "3DD2C897-F19E-4CA6-8C22-B027D5A71907",
|
||||
# "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
|
||||
# ],
|
||||
"AlbumInFolder": ["15uNd7%8RguTEgNPKHfTWw"],
|
||||
}
|
||||
|
||||
UUID_DICT = {"two_albums": "8SOE9s0XQVGsuq4ONohTng"}
|
||||
@@ -51,62 +49,57 @@ UUID_DICT = {"two_albums": "8SOE9s0XQVGsuq4ONohTng"}
|
||||
######### Test FolderInfo ##########
|
||||
|
||||
|
||||
def test_folders_1(caplog):
|
||||
def test_folders_1():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
|
||||
folders = photosdb.folders
|
||||
assert folders == []
|
||||
assert "Folders not yet implemented for this DB version" in caplog.text
|
||||
|
||||
# # top level folders
|
||||
# folders = photosdb.folders
|
||||
# assert len(folders) == 1
|
||||
# top level folders
|
||||
folders = photosdb.folder_info
|
||||
assert len(folders) == 1
|
||||
|
||||
# # check folder names
|
||||
# folder_names = [f.title for f in folders]
|
||||
# assert sorted(folder_names) == sorted(TOP_LEVEL_FOLDERS)
|
||||
# check folder names
|
||||
folder_names = [f.title for f in folders]
|
||||
assert sorted(folder_names) == sorted(TOP_LEVEL_FOLDERS)
|
||||
|
||||
|
||||
def test_folder_names(caplog):
|
||||
def test_folder_names():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
|
||||
# check folder names
|
||||
folder_names = photosdb.folders
|
||||
assert folder_names == []
|
||||
assert "Folders not yet implemented for this DB version" in caplog.text
|
||||
# assert sorted(folder_names) == sorted(TOP_LEVEL_FOLDERS)
|
||||
assert folder_names == 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():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
|
||||
# top level folders
|
||||
folders = photosdb.folders
|
||||
folders = photosdb.folder_info
|
||||
assert len(folders[0]) == len(TOP_LEVEL_CHILDREN)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Folders not yet impleted in Photos < 5")
|
||||
def test_folders_children():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
|
||||
# top level folders
|
||||
folders = photosdb.folders
|
||||
folders = photosdb.folder_info
|
||||
|
||||
# children of top level folder
|
||||
children = folders[0].folders
|
||||
children = folders[0].subfolders
|
||||
children_names = [f.title for f in 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
|
||||
assert child.parent
|
||||
assert child.parent.uuid == folders[0].uuid
|
||||
@@ -116,38 +109,36 @@ def test_folders_children():
|
||||
assert sorted(folder_names) == sorted(TOP_LEVEL_FOLDERS)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Folders not yet impleted in Photos < 5")
|
||||
def test_folders_parent():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
|
||||
# top level folders
|
||||
folders = photosdb.folders
|
||||
folders = photosdb.folder_info
|
||||
|
||||
# parent of top level folder should be none
|
||||
for folder in folders:
|
||||
assert folder.parent is None
|
||||
for child in folder.folders:
|
||||
for child in folder.subfolders:
|
||||
# children's parent uuid should match folder uuid
|
||||
assert child.parent
|
||||
assert child.parent.uuid == folder.uuid
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Folders not yet impleted in Photos < 5")
|
||||
def test_folders_albums():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
|
||||
# top level folders
|
||||
folders = photosdb.folders
|
||||
folders = photosdb.folder_info
|
||||
|
||||
for folder in folders:
|
||||
name = folder.title
|
||||
albums = [a.title for a in folder.album_info]
|
||||
assert sorted(albums) == sorted(FOLDER_ALBUM_DICT[name])
|
||||
for child in folder.folders:
|
||||
for child in folder.subfolders:
|
||||
name = child.title
|
||||
albums = [a.title for a in child.album_info]
|
||||
assert sorted(albums) == sorted(FOLDER_ALBUM_DICT[name])
|
||||
@@ -162,14 +153,14 @@ def test_albums_1():
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
|
||||
albums = photosdb.album_info
|
||||
assert len(albums) == 3
|
||||
assert len(albums) == 4
|
||||
|
||||
# check names
|
||||
album_names = [a.title for a in albums]
|
||||
assert sorted(album_names) == sorted(ALBUM_NAMES)
|
||||
|
||||
|
||||
def test_albums_parent(caplog):
|
||||
def test_albums_parent():
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
@@ -178,11 +169,10 @@ def test_albums_parent(caplog):
|
||||
|
||||
for album in albums:
|
||||
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
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
@@ -191,11 +181,10 @@ def test_albums_folder_names(caplog):
|
||||
|
||||
for album in albums:
|
||||
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
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
@@ -204,9 +193,8 @@ def test_albums_folders(caplog):
|
||||
|
||||
for album in albums:
|
||||
folders = album.folder_list
|
||||
assert "Folders not yet implemented for this DB version" in caplog.text
|
||||
# folder_names = [f.title for f in folders]
|
||||
# assert folder_names == ALBUM_FOLDER_NAMES_DICT[album.title]
|
||||
folder_names = [f.title for f in folders]
|
||||
assert folder_names == ALBUM_FOLDER_NAMES_DICT[album.title]
|
||||
|
||||
|
||||
def test_albums_len():
|
||||
|
||||
@@ -780,7 +780,7 @@ def test_no_folder_2_15():
|
||||
assert item["albums"] == ["AlbumInFolder"]
|
||||
|
||||
|
||||
def test_no_folder_1_14(caplog):
|
||||
def test_no_folder_1_14():
|
||||
# test --folder on 10.14
|
||||
import json
|
||||
import os
|
||||
@@ -797,6 +797,5 @@ def test_no_folder_1_14(caplog):
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
json_got = json.loads(result.output)
|
||||
|
||||
assert len(json_got) == 0 # single element
|
||||
assert "not yet implemented" in caplog.text
|
||||
assert len(json_got) == 1 # single element
|
||||
assert json_got[0]["uuid"] == "15uNd7%8RguTEgNPKHfTWw"
|
||||
|
||||
@@ -18,7 +18,7 @@ KEYWORDS = [
|
||||
"United Kingdom",
|
||||
]
|
||||
PERSONS = ["Katie", "Suzy", "Maria"]
|
||||
ALBUMS = ["Pumpkin Farm", "Test Album", "Test Album (1)"]
|
||||
ALBUMS = ["Pumpkin Farm", "AlbumInFolder", "Test Album", "Test Album (1)"]
|
||||
KEYWORDS_DICT = {
|
||||
"Kids": 4,
|
||||
"wedding": 2,
|
||||
@@ -31,7 +31,12 @@ KEYWORDS_DICT = {
|
||||
"United Kingdom": 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 = {
|
||||
"favorite": "6bxcNnzRQKGnK4uPrCJ9UQ",
|
||||
@@ -131,7 +136,9 @@ def test_attributes():
|
||||
)
|
||||
assert p.description == "Girl holding pumpkin"
|
||||
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.path.endswith(
|
||||
"/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_LIBRARY_PATH = "/Test-10.14.6.photoslibrary"
|
||||
|
||||
ALBUMS = ["Pumpkin Farm", "Test Album", "Test Album (1)"]
|
||||
ALBUM_DICT = {"Pumpkin Farm": 3, "Test Album": 1, "Test Album (1)": 1}
|
||||
ALBUMS = ["Pumpkin Farm", "AlbumInFolder", "Test Album", "Test Album (1)"]
|
||||
ALBUM_DICT = {
|
||||
"Pumpkin Farm": 3,
|
||||
"AlbumInFolder": 1,
|
||||
"Test Album": 1,
|
||||
"Test Album (1)": 1,
|
||||
}
|
||||
|
||||
|
||||
def test_album_names():
|
||||
|
||||
@@ -15,7 +15,7 @@ UUID_DICT = {
|
||||
"0_2_0": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
|
||||
"folder_album_1": "3DD2C897-F19E-4CA6-8C22-B027D5A71907",
|
||||
"folder_album_no_folder": "D79B8D77-BFFC-460B-9312-034F2877D35B",
|
||||
"mojave_no_folder": "15uNd7%8RguTEgNPKHfTWw",
|
||||
"mojave_album_1": "15uNd7%8RguTEgNPKHfTWw",
|
||||
}
|
||||
|
||||
TEMPLATE_VALUES = {
|
||||
@@ -341,17 +341,16 @@ def test_subst_multi_folder_albums_2():
|
||||
|
||||
|
||||
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
|
||||
from osxphotos.template import render_filepath_template
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB_14_6)
|
||||
|
||||
# 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}"
|
||||
expected = ["Pumpkin Farm", "Test Album (1)"]
|
||||
expected = ["Folder1/SubFolder2/AlbumInFolder", "Pumpkin Farm", "Test Album (1)"]
|
||||
rendered, unknown = render_filepath_template(template, photo)
|
||||
assert sorted(rendered) == sorted(expected)
|
||||
assert unknown == []
|
||||
assert "not yet implemented" in caplog.text
|
||||
|
||||
Reference in New Issue
Block a user