Initial support for movies
@ -103,6 +103,9 @@ def info(cli_obj):
|
|||||||
photos = pdb.photos()
|
photos = pdb.photos()
|
||||||
info["photo_count"] = len(photos)
|
info["photo_count"] = len(photos)
|
||||||
|
|
||||||
|
movies = pdb.photos(images=False, movies=True)
|
||||||
|
info["movie_count"] = len(movies)
|
||||||
|
|
||||||
keywords = pdb.keywords_as_dict
|
keywords = pdb.keywords_as_dict
|
||||||
info["keywords_count"] = len(keywords)
|
info["keywords_count"] = len(keywords)
|
||||||
info["keywords"] = keywords
|
info["keywords"] = keywords
|
||||||
@ -149,7 +152,7 @@ def info(cli_obj):
|
|||||||
def dump(cli_obj, json):
|
def dump(cli_obj, json):
|
||||||
""" Print list of all photos & associated info from the Photos library. """
|
""" Print list of all photos & associated info from the Photos library. """
|
||||||
pdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
pdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
||||||
photos = pdb.photos()
|
photos = pdb.photos(movies=True)
|
||||||
print_photo_info(photos, cli_obj.json or json)
|
print_photo_info(photos, cli_obj.json or json)
|
||||||
|
|
||||||
|
|
||||||
@ -627,7 +630,7 @@ def _query(
|
|||||||
""" if either is modified, need to ensure all three functions are updated """
|
""" if either is modified, need to ensure all three functions are updated """
|
||||||
|
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
photosdb = osxphotos.PhotosDB(dbfile=cli_obj.db)
|
||||||
photos = photosdb.photos(keywords=keyword, persons=person, albums=album, uuid=uuid)
|
photos = photosdb.photos(keywords=keyword, persons=person, albums=album, uuid=uuid, movies=True)
|
||||||
|
|
||||||
if title:
|
if title:
|
||||||
# search title field for text
|
# search title field for text
|
||||||
|
|||||||
@ -24,3 +24,7 @@ _EXIF_TOOL_URL = "https://exiftool.org/"
|
|||||||
|
|
||||||
# Where are shared iCloud photos located?
|
# Where are shared iCloud photos located?
|
||||||
_PHOTOS_5_SHARED_PHOTO_PATH = "resources/cloudsharing/data"
|
_PHOTOS_5_SHARED_PHOTO_PATH = "resources/cloudsharing/data"
|
||||||
|
|
||||||
|
# What type of file? Based on ZGENERICASSET.ZKIND in Photos 5 database
|
||||||
|
_PHOTO_TYPE = 0
|
||||||
|
_MOVIE_TYPE = 1
|
||||||
|
|||||||
@ -16,7 +16,12 @@ from pprint import pformat
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from ._constants import _PHOTOS_5_VERSION, _PHOTOS_5_SHARED_PHOTO_PATH
|
from ._constants import (
|
||||||
|
_MOVIE_TYPE,
|
||||||
|
_PHOTO_TYPE,
|
||||||
|
_PHOTOS_5_SHARED_PHOTO_PATH,
|
||||||
|
_PHOTOS_5_VERSION,
|
||||||
|
)
|
||||||
from .utils import _get_resource_loc, dd_to_dms_str
|
from .utils import _get_resource_loc, dd_to_dms_str
|
||||||
|
|
||||||
# TODO: check pylint output
|
# TODO: check pylint output
|
||||||
@ -122,6 +127,19 @@ class PhotoInfo:
|
|||||||
library = self._db._library_path
|
library = self._db._library_path
|
||||||
folder_id, file_id = _get_resource_loc(edit_id)
|
folder_id, file_id = _get_resource_loc(edit_id)
|
||||||
# todo: is this always true or do we need to search file file_id under folder_id
|
# todo: is this always true or do we need to search file file_id under folder_id
|
||||||
|
# figure out what kind it is and build filename
|
||||||
|
filename = None
|
||||||
|
if self._info["type"] == _PHOTO_TYPE:
|
||||||
|
# it's a photo
|
||||||
|
filename = f"fullsizeoutput_{file_id}.jpeg"
|
||||||
|
elif self._info["type"] == _MOVIE_TYPE:
|
||||||
|
# it's a movie
|
||||||
|
filename = f"fullsizeoutput_{file_id}.mov"
|
||||||
|
else:
|
||||||
|
# don't know what it is!
|
||||||
|
logging.debug(f"WARNING: unknown type {self._info['type']}")
|
||||||
|
return None
|
||||||
|
|
||||||
photopath = os.path.join(
|
photopath = os.path.join(
|
||||||
library,
|
library,
|
||||||
"resources",
|
"resources",
|
||||||
@ -129,7 +147,7 @@ class PhotoInfo:
|
|||||||
"version",
|
"version",
|
||||||
folder_id,
|
folder_id,
|
||||||
"00",
|
"00",
|
||||||
f"fullsizeoutput_{file_id}.jpeg",
|
filename,
|
||||||
)
|
)
|
||||||
if not os.path.isfile(photopath):
|
if not os.path.isfile(photopath):
|
||||||
logging.warning(
|
logging.warning(
|
||||||
@ -138,7 +156,7 @@ class PhotoInfo:
|
|||||||
photopath = None
|
photopath = None
|
||||||
else:
|
else:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"{self.uuid} hasAdjustments but edit_model_id is None"
|
f"{self.uuid} hasAdjustments but edit_resource_id is None"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
photopath = None
|
photopath = None
|
||||||
@ -267,6 +285,25 @@ class PhotoInfo:
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uti(self):
|
||||||
|
""" Returns Uniform Type Identifier (UTI) for the image
|
||||||
|
for example: public.jpeg or com.apple.quicktime-movie
|
||||||
|
"""
|
||||||
|
return self._info["UTI"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ismovie(self):
|
||||||
|
""" Returns True if file is a movie, otherwise False
|
||||||
|
"""
|
||||||
|
return True if self._info["type"] == _MOVIE_TYPE else False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isphoto(self):
|
||||||
|
""" Returns True if file is an image, otherwise False
|
||||||
|
"""
|
||||||
|
return True if self._info["type"] == _PHOTO_TYPE else False
|
||||||
|
|
||||||
def export(
|
def export(
|
||||||
self,
|
self,
|
||||||
dest,
|
dest,
|
||||||
|
|||||||
@ -20,6 +20,8 @@ from ._constants import (
|
|||||||
_TESTED_DB_VERSIONS,
|
_TESTED_DB_VERSIONS,
|
||||||
_TESTED_OS_VERSIONS,
|
_TESTED_OS_VERSIONS,
|
||||||
_UNKNOWN_PERSON,
|
_UNKNOWN_PERSON,
|
||||||
|
_PHOTO_TYPE,
|
||||||
|
_MOVIE_TYPE,
|
||||||
)
|
)
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
from .photoinfo import PhotoInfo
|
from .photoinfo import PhotoInfo
|
||||||
@ -381,11 +383,10 @@ class PhotosDB:
|
|||||||
(conn, c) = self._open_sql_file(self._tmp_db)
|
(conn, c) = self._open_sql_file(self._tmp_db)
|
||||||
|
|
||||||
# Look for all combinations of persons and pictures
|
# Look for all combinations of persons and pictures
|
||||||
# c.execute("select RKPerson.name, RKFace.imageID from RKFace, RKPerson where RKFace.personID = RKperson.modelID")
|
|
||||||
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.type = 2 and RKVersion.masterUuid = RKMaster.uuid and "
|
+ "and RKVersion.masterUuid = RKMaster.uuid and "
|
||||||
+ "RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
+ "RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
||||||
)
|
)
|
||||||
for person in c:
|
for person in c:
|
||||||
@ -398,10 +399,11 @@ class PhotosDB:
|
|||||||
self._dbfaces_uuid[person[1]].append(person[0])
|
self._dbfaces_uuid[person[1]].append(person[0])
|
||||||
self._dbfaces_person[person[0]].append(person[1])
|
self._dbfaces_person[person[0]].append(person[1])
|
||||||
|
|
||||||
|
# Get info on albums
|
||||||
c.execute(
|
c.execute(
|
||||||
"select RKAlbum.uuid, RKVersion.uuid from RKAlbum, RKVersion, RKAlbumVersion "
|
"select RKAlbum.uuid, RKVersion.uuid from RKAlbum, RKVersion, RKAlbumVersion "
|
||||||
+ "where RKAlbum.modelID = RKAlbumVersion.albumId and "
|
+ "where RKAlbum.modelID = RKAlbumVersion.albumId and "
|
||||||
+ "RKAlbumVersion.versionID = RKVersion.modelId and RKVersion.type = 2 and "
|
+ "RKAlbumVersion.versionID = RKVersion.modelId and "
|
||||||
+ "RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
+ "RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
||||||
)
|
)
|
||||||
for album in c:
|
for album in c:
|
||||||
@ -440,12 +442,13 @@ class PhotosDB:
|
|||||||
logging.debug(pformat(self._dbalbums_uuid))
|
logging.debug(pformat(self._dbalbums_uuid))
|
||||||
logging.debug(pformat(self._dbalbum_details))
|
logging.debug(pformat(self._dbalbum_details))
|
||||||
|
|
||||||
|
# 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 RKMaster.uuid = RKVersion.masterUuid and RKVersion.type = 2 "
|
+ "and RKMaster.uuid = RKVersion.masterUuid "
|
||||||
+ "and RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
+ "and RKVersion.filename not like '%.pdf' and RKVersion.isInTrash = 0"
|
||||||
)
|
)
|
||||||
for keyword in c:
|
for keyword in c:
|
||||||
@ -456,10 +459,12 @@ class PhotosDB:
|
|||||||
self._dbkeywords_uuid[keyword[1]].append(keyword[0])
|
self._dbkeywords_uuid[keyword[1]].append(keyword[0])
|
||||||
self._dbkeywords_keyword[keyword[0]].append(keyword[1])
|
self._dbkeywords_keyword[keyword[0]].append(keyword[1])
|
||||||
|
|
||||||
|
# Get info on disk volumes
|
||||||
c.execute("select RKVolume.modelId, RKVolume.name from RKVolume")
|
c.execute("select RKVolume.modelId, RKVolume.name from RKVolume")
|
||||||
for vol in c:
|
for vol in c:
|
||||||
self._dbvolumes[vol[0]] = vol[1]
|
self._dbvolumes[vol[0]] = vol[1]
|
||||||
|
|
||||||
|
# Get photo details
|
||||||
c.execute(
|
c.execute(
|
||||||
"select RKVersion.uuid, RKVersion.modelId, RKVersion.masterUuid, RKVersion.filename, "
|
"select RKVersion.uuid, RKVersion.modelId, RKVersion.masterUuid, RKVersion.filename, "
|
||||||
+ "RKVersion.lastmodifieddate, RKVersion.imageDate, RKVersion.mainRating, "
|
+ "RKVersion.lastmodifieddate, RKVersion.imageDate, RKVersion.mainRating, "
|
||||||
@ -467,8 +472,8 @@ class PhotosDB:
|
|||||||
+ "RKMaster.volumeId, RKMaster.imagePath, RKVersion.extendedDescription, RKVersion.name, "
|
+ "RKMaster.volumeId, RKMaster.imagePath, RKVersion.extendedDescription, RKVersion.name, "
|
||||||
+ "RKMaster.isMissing, RKMaster.originalFileName, RKVersion.isFavorite, RKVersion.isHidden, "
|
+ "RKMaster.isMissing, RKMaster.originalFileName, RKVersion.isFavorite, RKVersion.isHidden, "
|
||||||
+ "RKVersion.latitude, RKVersion.longitude, "
|
+ "RKVersion.latitude, RKVersion.longitude, "
|
||||||
+ "RKVersion.adjustmentUuid "
|
+ "RKVersion.adjustmentUuid, RKVersion.type, RKMaster.UTI "
|
||||||
+ "from RKVersion, RKMaster where RKVersion.isInTrash = 0 and RKVersion.type = 2 and "
|
+ "from RKVersion, RKMaster where RKVersion.isInTrash = 0 and "
|
||||||
+ "RKVersion.masterUuid = RKMaster.uuid and RKVersion.filename not like '%.pdf'"
|
+ "RKVersion.masterUuid = RKMaster.uuid and RKVersion.filename not like '%.pdf'"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -494,6 +499,8 @@ class PhotosDB:
|
|||||||
# 18 RKVersion.latitude
|
# 18 RKVersion.latitude
|
||||||
# 19 RKVersion.longitude
|
# 19 RKVersion.longitude
|
||||||
# 20 RKVersion.adjustmentUuid
|
# 20 RKVersion.adjustmentUuid
|
||||||
|
# 21 RKVersion.type
|
||||||
|
# 22 RKMaster.UTI
|
||||||
|
|
||||||
for row in c:
|
for row in c:
|
||||||
uuid = row[0]
|
uuid = row[0]
|
||||||
@ -533,6 +540,20 @@ class PhotosDB:
|
|||||||
self._dbphotos[uuid]["adjustmentUuid"] = row[20]
|
self._dbphotos[uuid]["adjustmentUuid"] = row[20]
|
||||||
self._dbphotos[uuid]["adjustmentFormatID"] = None
|
self._dbphotos[uuid]["adjustmentFormatID"] = None
|
||||||
|
|
||||||
|
# find type
|
||||||
|
if row[21] == 2:
|
||||||
|
# photo
|
||||||
|
self._dbphotos[uuid]["type"] = _PHOTO_TYPE
|
||||||
|
elif row[21] == 8:
|
||||||
|
# movie
|
||||||
|
self._dbphotos[uuid]["type"] = _MOVIE_TYPE
|
||||||
|
else:
|
||||||
|
# unknown
|
||||||
|
logging.debug(f"WARNING: {uuid} found unknown type {row[21]}")
|
||||||
|
self._dbphotos[uuid]["type"] = None
|
||||||
|
|
||||||
|
self._dbphotos[uuid]["UTI"] = row[22]
|
||||||
|
|
||||||
# get details needed to find path of the edited photos and live photos
|
# get details needed to find path of the edited photos and live photos
|
||||||
c.execute(
|
c.execute(
|
||||||
"SELECT RKVersion.uuid, RKVersion.adjustmentUuid, RKModelResource.modelId, "
|
"SELECT RKVersion.uuid, RKVersion.adjustmentUuid, RKModelResource.modelId, "
|
||||||
@ -555,6 +576,7 @@ class PhotosDB:
|
|||||||
|
|
||||||
# TODO: add live photos
|
# TODO: add live photos
|
||||||
# attachedmodeltype is 2, it's a photo, could be more than one
|
# attachedmodeltype is 2, it's a photo, could be more than one
|
||||||
|
# attachedmodeltype == 2 could also be movie?
|
||||||
# if 5, it's a facetile
|
# if 5, it's a facetile
|
||||||
# specialtype = 0 == image, 5 or 8 == live photo movie
|
# specialtype = 0 == image, 5 or 8 == live photo movie
|
||||||
|
|
||||||
@ -565,7 +587,7 @@ class PhotosDB:
|
|||||||
if (
|
if (
|
||||||
row[1] != "UNADJUSTEDNONRAW"
|
row[1] != "UNADJUSTEDNONRAW"
|
||||||
and row[1] != "UNADJUSTED"
|
and row[1] != "UNADJUSTED"
|
||||||
and row[4] == "public.jpeg"
|
# and row[4] == "public.jpeg"
|
||||||
and row[6] == 2
|
and row[6] == 2
|
||||||
):
|
):
|
||||||
if "edit_resource_id" in self._dbphotos[uuid]:
|
if "edit_resource_id" in self._dbphotos[uuid]:
|
||||||
@ -673,7 +695,7 @@ class PhotosDB:
|
|||||||
"SELECT ZPERSON.ZFULLNAME, ZGENERICASSET.ZUUID "
|
"SELECT ZPERSON.ZFULLNAME, ZGENERICASSET.ZUUID "
|
||||||
"FROM ZPERSON, ZDETECTEDFACE, ZGENERICASSET "
|
"FROM ZPERSON, ZDETECTEDFACE, ZGENERICASSET "
|
||||||
"WHERE ZDETECTEDFACE.ZPERSON = ZPERSON.Z_PK AND ZDETECTEDFACE.ZASSET = ZGENERICASSET.Z_PK "
|
"WHERE ZDETECTEDFACE.ZPERSON = ZPERSON.Z_PK AND ZDETECTEDFACE.ZASSET = ZGENERICASSET.Z_PK "
|
||||||
"AND ZGENERICASSET.ZTRASHEDSTATE = 0 AND ZGENERICASSET.ZKIND = 0 "
|
"AND ZGENERICASSET.ZTRASHEDSTATE = 0"
|
||||||
)
|
)
|
||||||
for person in c:
|
for person in c:
|
||||||
if person[0] is None:
|
if person[0] is None:
|
||||||
@ -694,7 +716,7 @@ class PhotosDB:
|
|||||||
"FROM ZGENERICASSET "
|
"FROM ZGENERICASSET "
|
||||||
"JOIN Z_26ASSETS ON Z_26ASSETS.Z_34ASSETS = ZGENERICASSET.Z_PK "
|
"JOIN Z_26ASSETS ON Z_26ASSETS.Z_34ASSETS = ZGENERICASSET.Z_PK "
|
||||||
"JOIN ZGENERICALBUM ON ZGENERICALBUM.Z_PK = Z_26ASSETS.Z_26ALBUMS "
|
"JOIN ZGENERICALBUM ON ZGENERICALBUM.Z_PK = Z_26ASSETS.Z_26ALBUMS "
|
||||||
"WHERE ZGENERICASSET.ZTRASHEDSTATE = 0 AND ZGENERICASSET.ZKIND = 0 "
|
"WHERE ZGENERICASSET.ZTRASHEDSTATE = 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
|
||||||
@ -732,13 +754,14 @@ class PhotosDB:
|
|||||||
logging.debug(pformat(self._dbalbums_uuid))
|
logging.debug(pformat(self._dbalbums_uuid))
|
||||||
logging.debug(pformat(self._dbalbum_details))
|
logging.debug(pformat(self._dbalbum_details))
|
||||||
|
|
||||||
|
# get details on keywords
|
||||||
c.execute(
|
c.execute(
|
||||||
"SELECT ZKEYWORD.ZTITLE, ZGENERICASSET.ZUUID "
|
"SELECT ZKEYWORD.ZTITLE, ZGENERICASSET.ZUUID "
|
||||||
"FROM ZGENERICASSET "
|
"FROM ZGENERICASSET "
|
||||||
"JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK "
|
"JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK "
|
||||||
"JOIN Z_1KEYWORDS ON Z_1KEYWORDS.Z_1ASSETATTRIBUTES = ZADDITIONALASSETATTRIBUTES.Z_PK "
|
"JOIN Z_1KEYWORDS ON Z_1KEYWORDS.Z_1ASSETATTRIBUTES = ZADDITIONALASSETATTRIBUTES.Z_PK "
|
||||||
"JOIN ZKEYWORD ON ZKEYWORD.Z_PK = Z_1KEYWORDS.Z_37KEYWORDS "
|
"JOIN ZKEYWORD ON ZKEYWORD.Z_PK = Z_1KEYWORDS.Z_37KEYWORDS "
|
||||||
"WHERE ZGENERICASSET.ZTRASHEDSTATE = 0 AND ZGENERICASSET.ZKIND = 0 "
|
"WHERE ZGENERICASSET.ZTRASHEDSTATE = 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:
|
||||||
@ -751,12 +774,14 @@ class PhotosDB:
|
|||||||
logging.debug(pformat(self._dbkeywords_keyword))
|
logging.debug(pformat(self._dbkeywords_keyword))
|
||||||
logging.debug(pformat(self._dbkeywords_uuid))
|
logging.debug(pformat(self._dbkeywords_uuid))
|
||||||
|
|
||||||
|
# get details on disk volumes
|
||||||
c.execute("SELECT ZUUID, ZNAME from ZFILESYSTEMVOLUME")
|
c.execute("SELECT ZUUID, ZNAME from ZFILESYSTEMVOLUME")
|
||||||
for vol in c:
|
for vol in c:
|
||||||
self._dbvolumes[vol[0]] = vol[1]
|
self._dbvolumes[vol[0]] = vol[1]
|
||||||
logging.debug(f"Finished walking through volumes")
|
logging.debug(f"Finished walking through volumes")
|
||||||
logging.debug(self._dbvolumes)
|
logging.debug(self._dbvolumes)
|
||||||
|
|
||||||
|
# get details about photos
|
||||||
logging.debug(f"Getting information about photos")
|
logging.debug(f"Getting information about photos")
|
||||||
c.execute(
|
c.execute(
|
||||||
"SELECT ZGENERICASSET.ZUUID, "
|
"SELECT ZGENERICASSET.ZUUID, "
|
||||||
@ -775,10 +800,12 @@ class PhotosDB:
|
|||||||
"ZGENERICASSET.ZLATITUDE, "
|
"ZGENERICASSET.ZLATITUDE, "
|
||||||
"ZGENERICASSET.ZLONGITUDE, "
|
"ZGENERICASSET.ZLONGITUDE, "
|
||||||
"ZGENERICASSET.ZHASADJUSTMENTS, "
|
"ZGENERICASSET.ZHASADJUSTMENTS, "
|
||||||
"ZGENERICASSET.ZCLOUDOWNERHASHEDPERSONID "
|
"ZGENERICASSET.ZCLOUDOWNERHASHEDPERSONID, "
|
||||||
|
"ZGENERICASSET.ZKIND, "
|
||||||
|
"ZGENERICASSET.ZUNIFORMTYPEIDENTIFIER "
|
||||||
"FROM ZGENERICASSET "
|
"FROM ZGENERICASSET "
|
||||||
"JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK "
|
"JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK "
|
||||||
"WHERE ZGENERICASSET.ZTRASHEDSTATE = 0 AND ZGENERICASSET.ZKIND = 0 "
|
"WHERE ZGENERICASSET.ZTRASHEDSTATE = 0 "
|
||||||
"ORDER BY ZGENERICASSET.ZUUID "
|
"ORDER BY ZGENERICASSET.ZUUID "
|
||||||
)
|
)
|
||||||
# Order of results
|
# Order of results
|
||||||
@ -799,6 +826,8 @@ class PhotosDB:
|
|||||||
# 14 "ZGENERICASSET.ZLONGITUDE, "
|
# 14 "ZGENERICASSET.ZLONGITUDE, "
|
||||||
# 15 "ZGENERICASSET.ZHASADJUSTMENTS "
|
# 15 "ZGENERICASSET.ZHASADJUSTMENTS "
|
||||||
# 16 "ZCLOUDOWNERHASHEDPERSONID " -- If not null, indicates a shared photo
|
# 16 "ZCLOUDOWNERHASHEDPERSONID " -- If not null, indicates a shared photo
|
||||||
|
# 17 "ZKIND," -- 0 = photo, 1 = movie
|
||||||
|
# 18 " ZUNIFORMTYPEIDENTIFIER " -- UTI
|
||||||
|
|
||||||
for row in c:
|
for row in c:
|
||||||
uuid = row[0]
|
uuid = row[0]
|
||||||
@ -848,6 +877,17 @@ class PhotosDB:
|
|||||||
self._dbphotos[uuid]["adjustmentUuid"] = None
|
self._dbphotos[uuid]["adjustmentUuid"] = None
|
||||||
self._dbphotos[uuid]["adjustmentFormatID"] = None
|
self._dbphotos[uuid]["adjustmentFormatID"] = None
|
||||||
|
|
||||||
|
# find type
|
||||||
|
if row[17] == 0:
|
||||||
|
self._dbphotos[uuid]["type"] = _PHOTO_TYPE
|
||||||
|
elif row[17] == 1:
|
||||||
|
self._dbphotos[uuid]["type"] = _MOVIE_TYPE
|
||||||
|
else:
|
||||||
|
logging.debug(f"WARNING: {uuid} found unknown type {row[17]}")
|
||||||
|
self._dbphotos[uuid]["type"] = None
|
||||||
|
|
||||||
|
self._dbphotos[uuid]["UTI"] = row[18]
|
||||||
|
|
||||||
# Get extended description
|
# Get extended description
|
||||||
c.execute(
|
c.execute(
|
||||||
"SELECT ZGENERICASSET.ZUUID, "
|
"SELECT ZGENERICASSET.ZUUID, "
|
||||||
@ -874,7 +914,7 @@ class PhotosDB:
|
|||||||
"FROM ZGENERICASSET, ZUNMANAGEDADJUSTMENT "
|
"FROM ZGENERICASSET, ZUNMANAGEDADJUSTMENT "
|
||||||
"JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK "
|
"JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK "
|
||||||
"WHERE ZADDITIONALASSETATTRIBUTES.ZUNMANAGEDADJUSTMENT = ZUNMANAGEDADJUSTMENT.Z_PK "
|
"WHERE ZADDITIONALASSETATTRIBUTES.ZUNMANAGEDADJUSTMENT = ZUNMANAGEDADJUSTMENT.Z_PK "
|
||||||
"AND ZGENERICASSET.ZTRASHEDSTATE = 0 AND ZGENERICASSET.ZKIND = 0 "
|
"AND ZGENERICASSET.ZTRASHEDSTATE = 0 "
|
||||||
)
|
)
|
||||||
for row in c:
|
for row in c:
|
||||||
uuid = row[0]
|
uuid = row[0]
|
||||||
@ -980,16 +1020,26 @@ class PhotosDB:
|
|||||||
logging.debug(pformat(self._dbphotos))
|
logging.debug(pformat(self._dbphotos))
|
||||||
|
|
||||||
# TODO: fix default values to None instead of []
|
# TODO: fix default values to None instead of []
|
||||||
def photos(self, keywords=[], uuid=[], persons=[], albums=[]):
|
def photos(
|
||||||
|
self,
|
||||||
|
keywords=None,
|
||||||
|
uuid=None,
|
||||||
|
persons=None,
|
||||||
|
albums=None,
|
||||||
|
images=True,
|
||||||
|
movies=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 no args, returns the entire database of photos
|
||||||
If called with args, returns photos matching the args (e.g. keywords, persons, etc.)
|
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)
|
If more than one arg, returns photos matching all the criteria (e.g. keywords AND persons)
|
||||||
|
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 False
|
||||||
"""
|
"""
|
||||||
photos_sets = [] # list of photo sets to perform intersection of
|
photos_sets = [] # list of photo sets to perform intersection of
|
||||||
if not keywords and not uuid and not persons and not albums:
|
if not keywords and not uuid and not persons and not albums:
|
||||||
# return all the photos
|
# return all the photos, filtering for images and movies
|
||||||
# append keys of all photos as a single set to photos_sets
|
# append keys of all photos as a single set to photos_sets
|
||||||
photos_sets.append(set(self._dbphotos.keys()))
|
photos_sets.append(set(self._dbphotos.keys()))
|
||||||
else:
|
else:
|
||||||
@ -1036,10 +1086,14 @@ class PhotosDB:
|
|||||||
photoinfo = []
|
photoinfo = []
|
||||||
if photos_sets: # found some photos
|
if photos_sets: # found some photos
|
||||||
# get the intersection of each argument/search criteria
|
# get the intersection of each argument/search criteria
|
||||||
logging.debug(f"Got here: {photos_sets}")
|
logging.debug(f"Got photo_sets: {photos_sets}")
|
||||||
for p in set.intersection(*photos_sets):
|
for p in set.intersection(*photos_sets):
|
||||||
info = PhotoInfo(db=self, uuid=p, info=self._dbphotos[p])
|
# filter for images and/or movies
|
||||||
photoinfo.append(info)
|
if (images and self._dbphotos[p]["type"] == _PHOTO_TYPE) or (
|
||||||
|
movies and self._dbphotos[p]["type"] == _MOVIE_TYPE
|
||||||
|
):
|
||||||
|
info = PhotoInfo(db=self, uuid=p, info=self._dbphotos[p])
|
||||||
|
photoinfo.append(info)
|
||||||
logging.debug(f"photoinfo: {pformat(photoinfo)}")
|
logging.debug(f"photoinfo: {pformat(photoinfo)}")
|
||||||
return photoinfo
|
return photoinfo
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||||
<date>2019-12-22T18:50:53Z</date>
|
<date>2019-12-28T01:09:58Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||||
<date>2019-12-23T14:57:50Z</date>
|
<date>2019-12-28T19:15:54Z</date>
|
||||||
</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>2019-12-22T07:53:26Z</date>
|
<date>2019-12-28T19:15:52Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -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>2019-12-22T07:56:25Z</date>
|
<date>2019-12-28T19:17:55Z</date>
|
||||||
<key>SnapshotTables</key>
|
<key>SnapshotTables</key>
|
||||||
<dict/>
|
<dict/>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 545 KiB |
|
After Width: | Height: | Size: 532 KiB |
|
After Width: | Height: | Size: 578 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 504 KiB |
|
After Width: | Height: | Size: 453 KiB |
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>DatabaseMinorVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>DatabaseVersion</key>
|
||||||
|
<integer>112</integer>
|
||||||
|
<key>LastOpenMode</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>LibrarySchemaVersion</key>
|
||||||
|
<integer>4025</integer>
|
||||||
|
<key>MetaSchemaVersion</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>createDate</key>
|
||||||
|
<date>2019-12-28T20:21:07Z</date>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
tests/Test-Movie-4_0.photoslibrary/database/RKAlbum_name.skindex
Normal file
BIN
tests/Test-Movie-4_0.photoslibrary/database/metaSchema.db
Normal file
BIN
tests/Test-Movie-4_0.photoslibrary/database/photos.db
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Photos</key>
|
||||||
|
<dict>
|
||||||
|
<key>CollapsedSidebarSectionIdentifiers</key>
|
||||||
|
<array/>
|
||||||
|
<key>ExpandedSidebarItemIdentifiers</key>
|
||||||
|
<array>
|
||||||
|
<string>TopLevelAlbums</string>
|
||||||
|
<string>TopLevelSlideshows</string>
|
||||||
|
</array>
|
||||||
|
<key>lastKnownItemCounts</key>
|
||||||
|
<dict>
|
||||||
|
<key>other</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
<key>photos</key>
|
||||||
|
<integer>6</integer>
|
||||||
|
<key>videos</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||||
|
<date>2019-12-28T20:21:09Z</date>
|
||||||
|
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||||
|
<date>2019-12-28T20:21:09Z</date>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>ProcessedInQuiescentState</key>
|
||||||
|
<true/>
|
||||||
|
<key>Version</key>
|
||||||
|
<integer>3</integer>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PVClustererBringUpState</key>
|
||||||
|
<integer>50</integer>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IncrementalPersonProcessingStage</key>
|
||||||
|
<integer>4</integer>
|
||||||
|
<key>PersonBuilderLastMinimumFaceGroupSizeForCreatingMergeCandidates</key>
|
||||||
|
<integer>15</integer>
|
||||||
|
<key>PersonBuilderMergeCandidatesEnabled</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 58 KiB |
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PLLanguageAndLocaleKey</key>
|
||||||
|
<string>en-US:en_US</string>
|
||||||
|
<key>PLLastGeoProviderIdKey</key>
|
||||||
|
<string>7618</string>
|
||||||
|
<key>PLLastLocationInfoFormatVer</key>
|
||||||
|
<integer>12</integer>
|
||||||
|
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>PLLastRevGeoVerFileFetchDateKey</key>
|
||||||
|
<date>2019-12-28T20:21:08Z</date>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>LastHistoryRowId</key>
|
||||||
|
<integer>212</integer>
|
||||||
|
<key>LibraryBuildTag</key>
|
||||||
|
<string>B6ED5567-1F65-47A8-B2E6-E7E0FA421F96</string>
|
||||||
|
<key>LibrarySchemaVersion</key>
|
||||||
|
<integer>4025</integer>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>FileVersion</key>
|
||||||
|
<integer>11</integer>
|
||||||
|
<key>Source</key>
|
||||||
|
<dict>
|
||||||
|
<key>35230</key>
|
||||||
|
<dict>
|
||||||
|
<key>CountryMinVersions</key>
|
||||||
|
<dict>
|
||||||
|
<key>OTHER</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>CurrentVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>NoResultErrorIsSuccess</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>57879</key>
|
||||||
|
<dict>
|
||||||
|
<key>CountryMinVersions</key>
|
||||||
|
<dict>
|
||||||
|
<key>OTHER</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
<key>CurrentVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>NoResultErrorIsSuccess</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>7618</key>
|
||||||
|
<dict>
|
||||||
|
<key>AddCountyIfNeeded</key>
|
||||||
|
<true/>
|
||||||
|
<key>CountryMinVersions</key>
|
||||||
|
<dict>
|
||||||
|
<key>OTHER</key>
|
||||||
|
<integer>10</integer>
|
||||||
|
</dict>
|
||||||
|
<key>CurrentVersion</key>
|
||||||
|
<integer>10</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 288 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 485 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 225 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 122 KiB |
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>DatabaseMinorVersion</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
<key>DatabaseVersion</key>
|
||||||
|
<integer>112</integer>
|
||||||
|
<key>HistoricalMarker</key>
|
||||||
|
<dict>
|
||||||
|
<key>LastHistoryRowId</key>
|
||||||
|
<integer>212</integer>
|
||||||
|
<key>LibraryBuildTag</key>
|
||||||
|
<string>B6ED5567-1F65-47A8-B2E6-E7E0FA421F96</string>
|
||||||
|
<key>LibrarySchemaVersion</key>
|
||||||
|
<integer>4025</integer>
|
||||||
|
</dict>
|
||||||
|
<key>LibrarySchemaVersion</key>
|
||||||
|
<integer>4025</integer>
|
||||||
|
<key>MetaSchemaVersion</key>
|
||||||
|
<integer>2</integer>
|
||||||
|
<key>SnapshotComplete</key>
|
||||||
|
<true/>
|
||||||
|
<key>SnapshotCompletedDate</key>
|
||||||
|
<date>2019-12-28T20:21:07Z</date>
|
||||||
|
<key>SnapshotLastValidated</key>
|
||||||
|
<date>2019-12-28T20:21:07Z</date>
|
||||||
|
<key>SnapshotTables</key>
|
||||||
|
<dict/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
tests/test-images/IMG_0670B_NOGPS.MOV
Normal file
BIN
tests/test-images/Pumkins1.jpg
Normal file
|
After Width: | Height: | Size: 545 KiB |
BIN
tests/test-images/Pumkins2.jpg
Normal file
|
After Width: | Height: | Size: 532 KiB |
BIN
tests/test-images/Pumpkins3.jpg
Normal file
|
After Width: | Height: | Size: 578 KiB |
BIN
tests/test-images/Pumpkins4.jpg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
tests/test-images/St James Park.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |