Cleaned up tests, fixed bug in PhotosDB.query

This commit is contained in:
Rhet Turnbull
2021-06-11 23:02:48 -07:00
parent 4b6c35b5f9
commit 0758f84dc4
48 changed files with 793 additions and 6017 deletions

View File

@@ -80,7 +80,7 @@ class PhotoInfo:
@property
def filename(self):
""" filename of the picture """
"""filename of the picture"""
if (
self._db._db_version <= _PHOTOS_4_VERSION
and self.has_raw
@@ -108,7 +108,7 @@ class PhotoInfo:
@property
def date(self):
""" image creation date as timezone aware datetime object """
"""image creation date as timezone aware datetime object"""
return self._info["imageDate"]
@property
@@ -134,12 +134,12 @@ class PhotoInfo:
@property
def tzoffset(self):
""" timezone offset from UTC in seconds """
"""timezone offset from UTC in seconds"""
return self._info["imageTimeZoneOffsetSeconds"]
@property
def path(self):
""" absolute path on disk of the original picture """
"""absolute path on disk of the original picture"""
try:
return self._path
except AttributeError:
@@ -211,7 +211,7 @@ class PhotoInfo:
@property
def path_edited(self):
""" absolute path on disk of the edited picture """
"""absolute path on disk of the edited picture"""
""" None if photo has not been edited """
try:
@@ -225,7 +225,7 @@ class PhotoInfo:
return self._path_edited
def _path_edited_5(self):
""" return path_edited for Photos >= 5 """
"""return path_edited for Photos >= 5"""
# In Photos 5.0 / Catalina / MacOS 10.15:
# edited photos appear to always be converted to .jpeg and stored in
# library_name/resources/renders/X/UUID_1_201_a.jpeg
@@ -283,7 +283,7 @@ class PhotoInfo:
return photopath
def _path_edited_4(self):
""" return path_edited for Photos <= 4 """
"""return path_edited for Photos <= 4"""
if self._db._db_version > _PHOTOS_4_VERSION:
raise RuntimeError("Wrong database format!")
@@ -343,7 +343,7 @@ class PhotoInfo:
@property
def path_raw(self):
""" absolute path of associated RAW image or None if there is not one """
"""absolute path of associated RAW image or None if there is not one"""
# In Photos 5, raw is in same folder as original but with _4.ext
# Unless "Copy Items to the Photos Library" is not checked
@@ -413,17 +413,17 @@ class PhotoInfo:
@property
def description(self):
""" long / extended description of picture """
"""long / extended description of picture"""
return self._info["extendedDescription"]
@property
def persons(self):
""" list of persons in picture """
"""list of persons in picture"""
return [self._db._dbpersons_pk[pk]["fullname"] for pk in self._info["persons"]]
@property
def person_info(self):
""" list of PersonInfo objects for person in picture """
"""list of PersonInfo objects for person in picture"""
try:
return self._personinfo
except AttributeError:
@@ -434,7 +434,7 @@ class PhotoInfo:
@property
def face_info(self):
""" list of FaceInfo objects for faces in picture """
"""list of FaceInfo objects for faces in picture"""
try:
return self._faceinfo
except AttributeError:
@@ -448,7 +448,7 @@ class PhotoInfo:
@property
def albums(self):
""" list of albums picture is contained in """
"""list of albums picture is contained in"""
try:
return self._albums
except AttributeError:
@@ -460,7 +460,7 @@ class PhotoInfo:
@property
def burst_albums(self):
"""If photo is burst photo, list of albums it is contained in as well as any albums the key photo is contained in, otherwise returns self.albums """
"""If photo is burst photo, list of albums it is contained in as well as any albums the key photo is contained in, otherwise returns self.albums"""
try:
return self._burst_albums
except AttributeError:
@@ -473,7 +473,7 @@ class PhotoInfo:
@property
def album_info(self):
""" list of AlbumInfo objects representing albums the photo is contained in """
"""list of AlbumInfo objects representing albums the photo is contained in"""
try:
return self._album_info
except AttributeError:
@@ -485,7 +485,7 @@ class PhotoInfo:
@property
def burst_album_info(self):
""" If photo is a burst photo, returns list of AlbumInfo objects representing albums the photo is contained in as well as albums the burst key photo is contained in, otherwise returns self.album_info. """
"""If photo is a burst photo, returns list of AlbumInfo objects representing albums the photo is contained in as well as albums the burst key photo is contained in, otherwise returns self.album_info."""
try:
return self._burst_album_info
except AttributeError:
@@ -498,7 +498,7 @@ class PhotoInfo:
@property
def import_info(self):
""" ImportInfo object representing import session for the photo or None if no import session """
"""ImportInfo object representing import session for the photo or None if no import session"""
try:
return self._import_info
except AttributeError:
@@ -511,17 +511,17 @@ class PhotoInfo:
@property
def keywords(self):
""" list of keywords for picture """
"""list of keywords for picture"""
return self._info["keywords"]
@property
def title(self):
""" name / title of picture """
"""name / title of picture"""
return self._info["name"]
@property
def uuid(self):
""" UUID of picture """
"""UUID of picture"""
return self._uuid
@property
@@ -539,12 +539,12 @@ class PhotoInfo:
@property
def hasadjustments(self):
""" True if picture has adjustments / edits """
"""True if picture has adjustments / edits"""
return self._info["hasAdjustments"] == 1
@property
def adjustments(self):
""" Returns AdjustmentsInfo class for adjustment data or None if no adjustments; Photos 5+ only """
"""Returns AdjustmentsInfo class for adjustment data or None if no adjustments; Photos 5+ only"""
if self._db._db_version <= _PHOTOS_4_VERSION:
return None
@@ -568,32 +568,32 @@ class PhotoInfo:
@property
def external_edit(self):
""" Returns True if picture was edited outside of Photos using external editor """
"""Returns True if picture was edited outside of Photos using external editor"""
return self._info["adjustmentFormatID"] == "com.apple.Photos.externalEdit"
@property
def favorite(self):
""" True if picture is marked as favorite """
"""True if picture is marked as favorite"""
return self._info["favorite"] == 1
@property
def hidden(self):
""" True if picture is hidden """
"""True if picture is hidden"""
return self._info["hidden"] == 1
@property
def visible(self):
""" True if picture is visble """
"""True if picture is visble"""
return self._info["visible"]
@property
def intrash(self):
""" True if picture is in trash ('Recently Deleted' folder)"""
"""True if picture is in trash ('Recently Deleted' folder)"""
return self._info["intrash"]
@property
def date_trashed(self):
""" Date asset was placed in the trash or None """
"""Date asset was placed in the trash or None"""
# TODO: add add_timezone(dt, offset_seconds) to datetime_utils
# also update date_modified
trasheddate = self._info["trasheddate"]
@@ -607,7 +607,7 @@ class PhotoInfo:
@property
def date_added(self):
""" Date photo was added to the database """
"""Date photo was added to the database"""
try:
return self._date_added
except AttributeError:
@@ -624,7 +624,7 @@ class PhotoInfo:
@property
def location(self):
""" returns (latitude, longitude) as float in degrees or None """
"""returns (latitude, longitude) as float in degrees or None"""
return (self._latitude, self._longitude)
@property
@@ -720,27 +720,27 @@ class PhotoInfo:
@property
def isreference(self):
""" Returns True if photo is a reference (not copied to the Photos library), otherwise False """
"""Returns True if photo is a reference (not copied to the Photos library), otherwise False"""
return self._info["isreference"]
@property
def burst(self):
""" Returns True if photo is part of a Burst photo set, otherwise False """
"""Returns True if photo is part of a Burst photo set, otherwise False"""
return self._info["burst"]
@property
def burst_selected(self):
""" Returns True if photo is a burst photo and has been selected from the burst set by the user, otherwise False """
"""Returns True if photo is a burst photo and has been selected from the burst set by the user, otherwise False"""
return bool(self._info["burstPickType"] & BURST_SELECTED)
@property
def burst_key(self):
""" Returns True if photo is a burst photo and is the key image for the burst set (the image that Photos shows on top of the burst stack), otherwise False """
"""Returns True if photo is a burst photo and is the key image for the burst set (the image that Photos shows on top of the burst stack), otherwise False"""
return bool(self._info["burstPickType"] & BURST_KEY)
@property
def burst_default_pick(self):
""" Returns True if photo is a burst image and is the photo that Photos selected as the default image for the burst set, otherwise False """
"""Returns True if photo is a burst image and is the photo that Photos selected as the default image for the burst set, otherwise False"""
return bool(self._info["burstPickType"] & BURST_DEFAULT_PICK)
@property
@@ -760,7 +760,7 @@ class PhotoInfo:
@property
def live_photo(self):
""" Returns True if photo is a live photo, otherwise False """
"""Returns True if photo is a live photo, otherwise False"""
return self._info["live_photo"]
@property
@@ -821,7 +821,7 @@ class PhotoInfo:
@property
def path_derivatives(self):
""" Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first) """
"""Return any derivative (preview) images associated with the photo as a list of paths, sorted by file size (largest first)"""
if self._db._db_version <= _PHOTOS_4_VERSION:
return self._path_derivatives_4()
@@ -838,7 +838,7 @@ class PhotoInfo:
return [str(filename) for filename in files if filename.suffix != ".THM"]
def _path_derivatives_4(self):
""" Return paths to all derivative (preview) files for Photos <= 4"""
"""Return paths to all derivative (preview) files for Photos <= 4"""
modelid = self._info["modelID"]
if modelid is None:
return []
@@ -875,42 +875,42 @@ class PhotoInfo:
@property
def panorama(self):
""" Returns True if photo is a panorama, otherwise False """
"""Returns True if photo is a panorama, otherwise False"""
return self._info["panorama"]
@property
def slow_mo(self):
""" Returns True if photo is a slow motion video, otherwise False """
"""Returns True if photo is a slow motion video, otherwise False"""
return self._info["slow_mo"]
@property
def time_lapse(self):
""" Returns True if photo is a time lapse video, otherwise False """
"""Returns True if photo is a time lapse video, otherwise False"""
return self._info["time_lapse"]
@property
def hdr(self):
""" Returns True if photo is an HDR photo, otherwise False """
"""Returns True if photo is an HDR photo, otherwise False"""
return self._info["hdr"]
@property
def screenshot(self):
""" Returns True if photo is an HDR photo, otherwise False """
"""Returns True if photo is an HDR photo, otherwise False"""
return self._info["screenshot"]
@property
def portrait(self):
""" Returns True if photo is a portrait, otherwise False """
"""Returns True if photo is a portrait, otherwise False"""
return self._info["portrait"]
@property
def selfie(self):
""" Returns True if photo is a selfie (front facing camera), otherwise False """
"""Returns True if photo is a selfie (front facing camera), otherwise False"""
return self._info["selfie"]
@property
def place(self):
""" Returns PlaceInfo object containing reverse geolocation info """
"""Returns PlaceInfo object containing reverse geolocation info"""
# implementation note: doesn't create the PlaceInfo object until requested
# then memoizes the object in self._place to avoid recreating the object
@@ -938,12 +938,12 @@ class PhotoInfo:
@property
def has_raw(self):
""" returns True if photo has an associated raw image (that is, it's a RAW+JPEG pair), otherwise False """
"""returns True if photo has an associated raw image (that is, it's a RAW+JPEG pair), otherwise False"""
return self._info["has_raw"]
@property
def israw(self):
""" returns True if photo is a raw image. For images with an associated RAW+JPEG pair, see has_raw """
"""returns True if photo is a raw image. For images with an associated RAW+JPEG pair, see has_raw"""
return "raw-image" in self.uti_original
@property
@@ -955,17 +955,17 @@ class PhotoInfo:
@property
def height(self):
""" returns height of the current photo version in pixels """
"""returns height of the current photo version in pixels"""
return self._info["height"]
@property
def width(self):
""" returns width of the current photo version in pixels """
"""returns width of the current photo version in pixels"""
return self._info["width"]
@property
def orientation(self):
""" returns EXIF orientation of the current photo version as int or 0 if current orientation cannot be determined """
"""returns EXIF orientation of the current photo version as int or 0 if current orientation cannot be determined"""
if self._db._db_version <= _PHOTOS_4_VERSION:
return self._info["orientation"]
@@ -981,27 +981,27 @@ class PhotoInfo:
@property
def original_height(self):
""" returns height of the original photo version in pixels """
"""returns height of the original photo version in pixels"""
return self._info["original_height"]
@property
def original_width(self):
""" returns width of the original photo version in pixels """
"""returns width of the original photo version in pixels"""
return self._info["original_width"]
@property
def original_orientation(self):
""" returns EXIF orientation of the original photo version as int """
"""returns EXIF orientation of the original photo version as int"""
return self._info["original_orientation"]
@property
def original_filesize(self):
""" returns filesize of original photo in bytes as int """
"""returns filesize of original photo in bytes as int"""
return self._info["original_filesize"]
@property
def duplicates(self):
""" return list of PhotoInfo objects for possible duplicates (matching signature of original size, date, height, width) or empty list if no matching duplicates """
"""return list of PhotoInfo objects for possible duplicates (matching signature of original size, date, height, width) or empty list if no matching duplicates"""
signature = self._db._duplicate_signature(self.uuid)
duplicates = []
try:
@@ -1060,12 +1060,12 @@ class PhotoInfo:
@property
def _longitude(self):
""" Returns longitude, in degrees """
"""Returns longitude, in degrees"""
return self._info["longitude"]
@property
def _latitude(self):
""" Returns latitude, in degrees """
"""Returns latitude, in degrees"""
return self._info["latitude"]
def _get_album_uuids(self):
@@ -1103,7 +1103,7 @@ class PhotoInfo:
return f"osxphotos.{self.__class__.__name__}(db={self._db}, uuid='{self._uuid}', info={self._info})"
def __str__(self):
""" string representation of PhotoInfo object """
"""string representation of PhotoInfo object"""
date_iso = self.date.isoformat()
date_modified_iso = (
@@ -1166,7 +1166,7 @@ class PhotoInfo:
return yaml.dump(info, sort_keys=False)
def asdict(self):
""" return dict representation """
"""return dict representation"""
folders = {album.title: album.folder_names for album in self.album_info}
exif = dataclasses.asdict(self.exif_info) if self.exif_info else {}
@@ -1242,7 +1242,7 @@ class PhotoInfo:
}
def json(self):
""" Return JSON representation """
"""Return JSON representation"""
def default(o):
if isinstance(o, (datetime.date, datetime.datetime)):
@@ -1251,7 +1251,7 @@ class PhotoInfo:
return json.dumps(self.asdict(), sort_keys=True, default=default)
def __eq__(self, other):
""" Compare two PhotoInfo objects for equality """
"""Compare two PhotoInfo objects for equality"""
# Can't just compare the two __dicts__ because some methods (like albums)
# memoize their value once called in an instance variable (e.g. self._albums)
if isinstance(other, self.__class__):
@@ -1263,5 +1263,9 @@ class PhotoInfo:
return False
def __ne__(self, other):
""" Compare two PhotoInfo objects for inequality """
"""Compare two PhotoInfo objects for inequality"""
return not self.__eq__(other)
def __hash__(self):
"""Make PhotoInfo hashable"""
return hash(self.uuid)

View File

@@ -62,7 +62,7 @@ from .photosdb_utils import get_db_model_version, get_db_version
class PhotosDB:
""" Processes a Photos.app library database to extract information about photos """
"""Processes a Photos.app library database to extract information about photos"""
# import additional methods
from ._photosdb_process_exif import _process_exifinfo
@@ -78,13 +78,13 @@ class PhotosDB:
from ._photosdb_process_comments import _process_comments
def __init__(self, dbfile=None, verbose=None, exiftool=None):
""" Create a new PhotosDB object.
"""Create a new PhotosDB object.
Args:
dbfile: specify full path to photos library or photos.db; if None, will attempt to locate last library opened by Photos.
verbose: optional callable function to use for printing verbose text during processing; if None (default), does not print output.
exiftool: optional path to exiftool for methods that require this (e.g. PhotoInfo.exiftool); if not provided, will search PATH
Raises:
FileNotFoundError if dbfile is not a valid Photos library.
TypeError if verbose is not None and not callable.
@@ -326,7 +326,7 @@ class PhotosDB:
@property
def keywords_as_dict(self):
""" return keywords as dict of keyword, count in reverse sorted order (descending) """
"""return keywords as dict of keyword, count in reverse sorted order (descending)"""
keywords = {
k: len(self._dbkeywords_keyword[k]) for k in self._dbkeywords_keyword.keys()
}
@@ -336,7 +336,7 @@ class PhotosDB:
@property
def persons_as_dict(self):
""" return persons as dict of person, count in reverse sorted order (descending) """
"""return persons as dict of person, count in reverse sorted order (descending)"""
persons = {}
for pk in self._dbfaces_pk:
fullname = self._dbpersons_pk[pk]["fullname"]
@@ -349,7 +349,7 @@ class PhotosDB:
@property
def albums_as_dict(self):
""" return albums as dict of albums, count in reverse sorted order (descending) """
"""return albums as dict of albums, count in reverse sorted order (descending)"""
albums = {}
album_keys = self._get_album_uuids(shared=False)
for album in album_keys:
@@ -366,8 +366,8 @@ class PhotosDB:
@property
def albums_shared_as_dict(self):
""" returns shared albums as dict of albums, count in reverse sorted order (descending)
valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict """
"""returns shared albums as dict of albums, count in reverse sorted order (descending)
valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict"""
albums = {}
album_keys = self._get_album_uuids(shared=True)
@@ -385,19 +385,19 @@ class PhotosDB:
@property
def keywords(self):
""" return list of keywords found in photos database """
"""return list of keywords found in photos database"""
keywords = self._dbkeywords_keyword.keys()
return list(keywords)
@property
def persons(self):
""" return list of persons found in photos database """
"""return list of persons found in photos database"""
persons = {self._dbpersons_pk[k]["fullname"] for k in self._dbfaces_pk}
return list(persons)
@property
def person_info(self):
""" return list of PersonInfo objects for each person in the photos database """
"""return list of PersonInfo objects for each person in the photos database"""
try:
return self._person_info
except AttributeError:
@@ -408,7 +408,7 @@ class PhotosDB:
@property
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_4_VERSION:
folders = [
FolderInfo(db=self, uuid=folder)
@@ -429,7 +429,7 @@ class PhotosDB:
@property
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_4_VERSION:
folder_names = [
folder["name"]
@@ -450,7 +450,7 @@ class PhotosDB:
@property
def album_info(self):
""" return list of AlbumInfo objects for each album in the photos database """
"""return list of AlbumInfo objects for each album in the photos database"""
try:
return self._album_info
except AttributeError:
@@ -462,8 +462,8 @@ class PhotosDB:
@property
def album_info_shared(self):
""" return list of AlbumInfo objects for each shared album in the photos database
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list """
"""return list of AlbumInfo objects for each shared album in the photos database
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list"""
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
try:
return self._album_info_shared
@@ -476,7 +476,7 @@ class PhotosDB:
@property
def albums(self):
""" return list of albums found in photos database """
"""return list of albums found in photos database"""
# Could be more than one album with same name
# Right now, they are treated as same album and photos are combined from albums with same name
@@ -489,8 +489,8 @@ class PhotosDB:
@property
def albums_shared(self):
""" return list of shared albums found in photos database
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list """
"""return list of shared albums found in photos database
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list"""
# Could be more than one album with same name
# Right now, they are treated as same album and photos are combined from albums with same name
@@ -505,7 +505,7 @@ class PhotosDB:
@property
def import_info(self):
""" return list of ImportInfo objects for each import session in the database """
"""return list of ImportInfo objects for each import session in the database"""
try:
return self._import_info
except AttributeError:
@@ -517,21 +517,21 @@ class PhotosDB:
@property
def db_version(self):
""" return the database version as stored in LiGlobals table """
"""return the database version as stored in LiGlobals table"""
return self._db_version
@property
def db_path(self):
""" returns path to the Photos library database PhotosDB was initialized with """
"""returns path to the Photos library database PhotosDB was initialized with"""
return os.path.abspath(self._dbfile)
@property
def library_path(self):
""" returns path to the Photos library PhotosDB was initialized with """
"""returns path to the Photos library PhotosDB was initialized with"""
return self._library_path
def get_db_connection(self):
""" Get connection to the working copy of the Photos database
"""Get connection to the working copy of the Photos database
Returns:
tuple of (connection, cursor) to sqlite3 database
@@ -539,7 +539,7 @@ class PhotosDB:
return _open_sql_file(self._tmp_db)
def _copy_db_file(self, fname):
""" copies the sqlite database file to a temp file """
"""copies the sqlite database file to a temp file"""
""" returns the name of the temp file """
""" If sqlite shared memory and write-ahead log files exist, those are copied too """
# required because python's sqlite3 implementation can't read a locked file
@@ -591,8 +591,8 @@ class PhotosDB:
# return dest_path
def _process_database4(self):
""" process the Photos database to extract info
works on Photos version <= 4.0 """
"""process the Photos database to extract info
works on Photos version <= 4.0"""
verbose = self._verbose
verbose("Processing database.")
@@ -1543,15 +1543,15 @@ class PhotosDB:
logging.debug(pformat(self._dbphotos_burst))
def _build_album_folder_hierarchy_4(self, uuid, folders=None):
""" recursively build folder/album hierarchy
uuid: parent uuid of the album being processed
(parent uuid is a folder in RKFolders)
folders: dict holding the folder hierarchy
NOTE: This implementation is different than _build_album_folder_hierarchy_5
which takes the uuid of the album being processed. Here uuid is the parent uuid
of the parent folder album because in Photos <=4, folders are in RKFolders and
albums in RKAlbums. In Photos 5, folders are just special albums
with kind = _PHOTOS_5_FOLDER_KIND """
"""recursively build folder/album hierarchy
uuid: parent uuid of the album being processed
(parent uuid is a folder in RKFolders)
folders: dict holding the folder hierarchy
NOTE: This implementation is different than _build_album_folder_hierarchy_5
which takes the uuid of the album being processed. Here uuid is the parent uuid
of the parent folder album because in Photos <=4, folders are in RKFolders and
albums in RKAlbums. In Photos 5, folders are just special albums
with kind = _PHOTOS_5_FOLDER_KIND"""
parent_uuid = self._dbfolder_details[uuid]["parentFolderUuid"]
@@ -1574,11 +1574,11 @@ class PhotosDB:
return folders
def _process_database5(self):
""" process the Photos database to extract info
works on Photos version 5 and version 6
"""process the Photos database to extract info
works on Photos version 5 and version 6
This is a big hairy 700 line function that should probably be refactored
but it works so don't touch it.
This is a big hairy 700 line function that should probably be refactored
but it works so don't touch it.
"""
if _debug():
@@ -2448,9 +2448,9 @@ class PhotosDB:
logging.debug(pformat(self._dbphotos_burst))
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 """
"""recursively build folder/album hierarchy
uuid: uuid of the album/folder being processed
folders: dict holding the folder hierarchy"""
# get parent uuid
parent = self._dbalbum_details[uuid]["parentfolder"]
@@ -2471,17 +2471,17 @@ class PhotosDB:
return folders
def _album_folder_hierarchy_list(self, album_uuid):
""" return appropriate album_folder_hierarchy_list for the _db_version """
"""return appropriate album_folder_hierarchy_list for the _db_version"""
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 """
"""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"""
try:
folders = self._dbalbum_folders[album_uuid]
except KeyError:
@@ -2489,7 +2489,7 @@ class PhotosDB:
return []
def _recurse_folder_hierarchy(folders, hierarchy=[]):
""" recursively walk the folders dict to build list of folder hierarchy """
"""recursively walk the folders dict to build list of folder hierarchy"""
if not folders:
# empty folder dict (album has no folder hierarchy)
return []
@@ -2515,10 +2515,10 @@ class PhotosDB:
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"]
returns empty list of album is not in any folders """
"""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"""
try:
folders = self._dbalbum_folders[album_uuid]
except KeyError:
@@ -2526,7 +2526,7 @@ class PhotosDB:
return []
def _recurse_folder_hierarchy(folders, hierarchy=[]):
""" recursively walk the folders dict to build list of folder hierarchy """
"""recursively walk the folders dict to build list of folder hierarchy"""
if not folders:
# empty folder dict (album has no folder hierarchy)
@@ -2558,15 +2558,15 @@ class PhotosDB:
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 """
"""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 """
"""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)
@@ -2592,14 +2592,14 @@ class PhotosDB:
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 """
"""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]
def _recurse_folder_hierarchy(folders, hierarchy=[]):
""" recursively walk the folders dict to build list of folder hierarchy """
"""recursively walk the folders dict to build list of folder hierarchy"""
if not folders:
# empty folder dict (album has no folder hierarchy)
@@ -2624,19 +2624,19 @@ class PhotosDB:
return hierarchy
def _get_album_uuids(self, shared=False, import_session=False):
""" Return list of album UUIDs found in photos database
"""Return list of album UUIDs found in photos database
Filters out albums in the trash and any special album types
Args:
shared: boolean; if True, returns shared albums, else normal albums
import_session: boolean, if True, returns import session albums, else normal or shared albums
Note: flags (shared, import_session) are mutually exclusive
Raises:
ValueError: raised if mutually exclusive flags passed
Returns: list of album UUIDs
Returns: list of album UUIDs
"""
if shared and import_session:
raise ValueError(
@@ -2688,14 +2688,14 @@ class PhotosDB:
return album_list
def _get_albums(self, shared=False):
""" Return list of album titles found in photos database
"""Return list of album titles found in photos database
Albums may have duplicate titles -- these will be treated as a single album.
Filters out albums in the trash and any special album types
Args:
shared: boolean; if True, returns shared albums, else normal albums
Returns: list of album names
"""
@@ -2714,7 +2714,7 @@ class PhotosDB:
to_date=None,
intrash=False,
):
""" Return a list of PhotoInfo objects
"""Return a list of PhotoInfo objects
If called with no args, returns the entire database of photos
If called with args, returns photos matching the args (e.g. keywords, persons, etc.)
If more than one arg, returns photos matching all the criteria (e.g. keywords AND persons)
@@ -2729,10 +2729,10 @@ class PhotosDB:
persons: list of persons to search for
albums: list of album names to search for
images: if True, returns image files, if False, does not return images; default is True
movies: if True, returns movie files, if False, does not return movies; default is True
movies: if True, returns movie files, if False, does not return movies; default is True
from_date: return photos with creation date >= from_date (datetime.datetime object, default None)
to_date: return photos with creation date <= to_date (datetime.datetime object, default None)
intrash: if True, returns only images in "Recently deleted items" folder,
intrash: if True, returns only images in "Recently deleted items" folder,
if False returns only photos that aren't deleted; default is False
Returns:
@@ -2839,7 +2839,7 @@ class PhotosDB:
return photoinfo
def get_photo(self, uuid):
""" Returns a single photo matching uuid
"""Returns a single photo matching uuid
Arguments:
uuid: the UUID of photo to get
@@ -2854,7 +2854,7 @@ class PhotosDB:
# TODO: add to docs and test
def photos_by_uuid(self, uuids):
""" Returns a list of photos with UUID in uuids.
"""Returns a list of photos with UUID in uuids.
Does not generate error if invalid or missing UUID passed.
This is faster than using PhotosDB.photos if you have list of UUIDs.
Returns photos regardless of intrash state.
@@ -3228,7 +3228,7 @@ class PhotosDB:
return photos
def _duplicate_signature(self, uuid):
""" Compute a signature for finding possible duplicates """
"""Compute a signature for finding possible duplicates"""
return (
self._dbphotos[uuid]["original_filesize"],
self._dbphotos[uuid]["imageDate"],
@@ -3249,8 +3249,8 @@ class PhotosDB:
return False
def __len__(self):
""" Returns number of photos in the database
Includes recently deleted photos and non-selected burst images
"""Returns number of photos in the database
Includes recently deleted photos and non-selected burst images
"""
return len(self._dbphotos)
@@ -3280,4 +3280,4 @@ def _get_photos_by_attribute(photos, attribute, values, ignore_case):
else:
for x in values:
photos_search.extend(p for p in photos if x in getattr(p, attribute))
return photos_search
return list(set(photos_search))