Alpha support for MacOS Big Sur/10.16, see issue #187

This commit is contained in:
Rhet Turnbull
2020-08-09 10:59:05 -07:00
parent d0ec8620c7
commit 6acf9acd63
197 changed files with 2024 additions and 125 deletions

View File

@@ -10,18 +10,42 @@ import os.path
# Photos 4.0 (10.14.5) == 4016
# Photos 4.0 (10.14.6) == 4025
# Photos 5.0 (10.15.0) == 6000
# TODO: Should this also use compatibleBackToVersion from LiGlobals?
_TESTED_DB_VERSIONS = ["6000", "4025", "4016", "3301", "2622"]
# database model versions (applies to Photos 5, Photos 6)
# these come from PLModelVersion key in binary plist in Z_METADATA.Z_PLIST
# Photos 5 (10.15.1) == 13537
# Photos 5 (10.15.4, 10.15.5, 10.15.6) == 13703
# Photos 6 (10.16.0 Beta) == 14104
_TEST_MODEL_VERSIONS = ["13537", "13703", "14104"]
# only version 3 - 4 have RKVersion.selfPortrait
_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.15.5
_PHOTOS_5_VERSION = "6000" # seems to be current on 10.15.1 through 10.15.6
# Ranges for model version by Photos version
_PHOTOS_5_MODEL_VERSION = [13000, 13999]
_PHOTOS_6_MODEL_VERSION = [14000, 14999]
# some table names differ between Photos 5 and Photos 6
_DB_TABLE_NAMES = {
5: {
"ASSET": "ZGENERICASSET",
"KEYWORD_JOIN": "Z_1KEYWORDS.Z_37KEYWORDS",
"ALBUM_JOIN": "Z_26ASSETS.Z_34ASSETS",
},
6: {
"ASSET": "ZASSET",
"KEYWORD_JOIN": "Z_1KEYWORDS.Z_36KEYWORDS",
"ALBUM_JOIN": "Z_26ASSETS.Z_3ASSETS",
},
}
# which major version operating systems have been tested
_TESTED_OS_VERSIONS = ["12", "13", "14", "15"]
_TESTED_OS_VERSIONS = ["12", "13", "14", "15", "16"]
# Photos 5 has persons who are empty string if unidentified face
_UNKNOWN_PERSON = "_UNKNOWN_"

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.31.2"
__version__ = "0.32.0"

View File

@@ -4,3 +4,4 @@ Processes a Photos.app library database to extract information about photos
"""
from .photosdb import PhotosDB
from .photosdb_utils import get_db_version, get_db_model_version, get_model_version

View File

@@ -3,9 +3,9 @@
import logging
from .._constants import _PHOTOS_4_VERSION
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
from ..utils import _db_is_locked, _debug, _open_sql_file
from .photosdb_utils import get_db_version
def _process_exifinfo(self):
""" load the exif data from the database
@@ -35,14 +35,16 @@ def _process_exifinfo_5(photosdb):
db = photosdb._tmp_db
asset_table = _DB_TABLE_NAMES[photosdb._photos_ver]["ASSET"]
(conn, cursor) = _open_sql_file(db)
result = conn.execute(
"""
SELECT ZGENERICASSET.ZUUID, ZEXTENDEDATTRIBUTES.*
FROM ZGENERICASSET
f"""
SELECT {asset_table}.ZUUID, ZEXTENDEDATTRIBUTES.*
FROM {asset_table}
JOIN ZEXTENDEDATTRIBUTES
ON ZEXTENDEDATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK
ON ZEXTENDEDATTRIBUTES.ZASSET = {asset_table}.Z_PK
"""
)

View File

@@ -3,8 +3,9 @@
import logging
from .._constants import _PHOTOS_4_VERSION
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
from ..utils import _open_sql_file
from .photosdb_utils import get_db_version
"""
@@ -180,13 +181,15 @@ def _process_faceinfo_5(photosdb):
db = photosdb._tmp_db
asset_table = _DB_TABLE_NAMES[photosdb._photos_ver]["ASSET"]
(conn, cursor) = _open_sql_file(db)
result = cursor.execute(
"""
f"""
SELECT
ZDETECTEDFACE.Z_PK,
ZGENERICASSET.ZUUID,
{asset_table}.ZUUID,
ZDETECTEDFACE.ZUUID,
ZDETECTEDFACE.ZPERSON,
ZPERSON.ZFULLNAME,
@@ -225,7 +228,7 @@ def _process_faceinfo_5(photosdb):
ZDETECTEDFACE.ZYAW,
ZDETECTEDFACE.ZMASTERIDENTIFIER
FROM ZDETECTEDFACE
JOIN ZGENERICASSET ON ZGENERICASSET.Z_PK = ZDETECTEDFACE.ZASSET
JOIN {asset_table} ON {asset_table}.Z_PK = ZDETECTEDFACE.ZASSET
JOIN ZPERSON ON ZPERSON.Z_PK = ZDETECTEDFACE.ZPERSON;
"""
)

View File

@@ -4,8 +4,9 @@
import logging
from .._constants import _PHOTOS_4_VERSION
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
from ..utils import _open_sql_file
from .photosdb_utils import get_db_version
"""
This module should be imported in the class defintion of PhotosDB in photosdb.py
@@ -45,16 +46,18 @@ def _process_scoreinfo_5(photosdb):
db = photosdb._tmp_db
asset_table = _DB_TABLE_NAMES[photosdb._photos_ver]["ASSET"]
(conn, cursor) = _open_sql_file(db)
result = cursor.execute(
"""
f"""
SELECT
ZGENERICASSET.ZUUID,
ZGENERICASSET.ZOVERALLAESTHETICSCORE,
ZGENERICASSET.ZCURATIONSCORE,
ZGENERICASSET.ZPROMOTIONSCORE,
ZGENERICASSET.ZHIGHLIGHTVISIBILITYSCORE,
{asset_table}.ZUUID,
{asset_table}.ZOVERALLAESTHETICSCORE,
{asset_table}.ZCURATIONSCORE,
{asset_table}.ZPROMOTIONSCORE,
{asset_table}.ZHIGHLIGHTVISIBILITYSCORE,
ZCOMPUTEDASSETATTRIBUTES.ZBEHAVIORALSCORE,
ZCOMPUTEDASSETATTRIBUTES.ZFAILURESCORE,
ZCOMPUTEDASSETATTRIBUTES.ZHARMONIOUSCOLORSCORE,
@@ -78,8 +81,8 @@ def _process_scoreinfo_5(photosdb):
ZCOMPUTEDASSETATTRIBUTES.ZWELLCHOSENSUBJECTSCORE,
ZCOMPUTEDASSETATTRIBUTES.ZWELLFRAMEDSUBJECTSCORE,
ZCOMPUTEDASSETATTRIBUTES.ZWELLTIMEDSHOTSCORE
FROM ZGENERICASSET
JOIN ZCOMPUTEDASSETATTRIBUTES ON ZCOMPUTEDASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK
FROM {asset_table}
JOIN ZCOMPUTEDASSETATTRIBUTES ON ZCOMPUTEDASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
"""
)

View File

@@ -16,6 +16,7 @@ from pprint import pformat
from shutil import copyfile
from .._constants import (
_DB_TABLE_NAMES,
_MOVIE_TYPE,
_PHOTO_TYPE,
_PHOTOS_3_VERSION,
@@ -45,6 +46,7 @@ from ..utils import (
_open_sql_file,
get_last_library_path,
)
from .photosdb_utils import get_db_version, get_db_model_version
# TODO: Add test for imageTimeZoneOffsetSeconds = None
# TODO: Add test for __str__
@@ -267,7 +269,7 @@ class PhotosDB:
if _db_is_locked(self._dbfile):
self._tmp_db = self._copy_db_file(self._dbfile)
self._db_version = self._get_db_version(self._tmp_db)
self._db_version = get_db_version(self._tmp_db)
# If Photos >= 5, actual data isn't in photos.db but in Photos.sqlite
if int(self._db_version) > int(_PHOTOS_4_VERSION):
@@ -530,34 +532,6 @@ class PhotosDB:
return dest_path
def _get_db_version(self, db_file):
""" Gets the Photos DB version from LiGlobals table
Args:
db_file: path to database file containing LiGlobals table
Returns: version as str
"""
version = None
(conn, c) = _open_sql_file(db_file)
# get database version
c.execute(
"SELECT value from LiGlobals where LiGlobals.keyPath is 'libraryVersion'"
)
version = c.fetchone()[0]
conn.close()
if version not in _TESTED_DB_VERSIONS:
print(
f"WARNING: Only tested on database versions [{', '.join(_TESTED_DB_VERSIONS)}]"
+ f" You have database version={version} which has not been tested"
)
return version
def _process_database4(self):
""" process the Photos database to extract info
works on Photos version <= 4.0 """
@@ -1436,7 +1410,7 @@ class PhotosDB:
def _process_database5(self):
""" process the Photos database to extract info
works on Photos version >= 5.0
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.
@@ -1448,6 +1422,12 @@ class PhotosDB:
# Epoch is Jan 1, 2001
td = (datetime(2001, 1, 1, 0, 0) - datetime(1970, 1, 1, 0, 0)).total_seconds()
photos_ver = get_db_model_version(self._tmp_db)
self._photos_ver = photos_ver
asset_table = _DB_TABLE_NAMES[photos_ver]["ASSET"]
keyword_join = _DB_TABLE_NAMES[photos_ver]["KEYWORD_JOIN"]
album_join = _DB_TABLE_NAMES[photos_ver]["ALBUM_JOIN"]
(conn, c) = _open_sql_file(self._tmp_db)
# Look for all combinations of persons and pictures
@@ -1496,14 +1476,14 @@ class PhotosDB:
# get info on keyface -- some photos have null keyface so can't do a single query
# (at least not with my SQL skills)
c.execute(
""" SELECT
f""" SELECT
ZPERSON.Z_PK,
ZPERSON.ZKEYFACE,
ZGENERICASSET.ZUUID,
{asset_table}.ZUUID,
ZDETECTEDFACE.ZUUID
FROM ZPERSON, ZDETECTEDFACE, ZGENERICASSET
FROM ZPERSON, ZDETECTEDFACE, {asset_table}
WHERE ZDETECTEDFACE.Z_PK = ZPERSON.ZKEYFACE AND
ZDETECTEDFACE.ZASSET = ZGENERICASSET.Z_PK
ZDETECTEDFACE.ZASSET = {asset_table}.Z_PK
"""
)
@@ -1522,12 +1502,12 @@ class PhotosDB:
# get information on detected faces
c.execute(
""" SELECT
f""" SELECT
ZPERSON.Z_PK,
ZGENERICASSET.ZUUID
FROM ZPERSON, ZDETECTEDFACE, ZGENERICASSET
{asset_table}.ZUUID
FROM ZPERSON, ZDETECTEDFACE, {asset_table}
WHERE ZDETECTEDFACE.ZPERSON = ZPERSON.Z_PK AND
ZDETECTEDFACE.ZASSET = ZGENERICASSET.Z_PK
ZDETECTEDFACE.ZASSET = {asset_table}.Z_PK
"""
)
@@ -1556,12 +1536,12 @@ class PhotosDB:
# get details about albums
c.execute(
""" SELECT
f""" SELECT
ZGENERICALBUM.ZUUID,
ZGENERICASSET.ZUUID,
Z_26ASSETS.Z_FOK_34ASSETS
FROM ZGENERICASSET
JOIN Z_26ASSETS ON Z_26ASSETS.Z_34ASSETS = ZGENERICASSET.Z_PK
{asset_table}.ZUUID,
{album_join}
FROM {asset_table}
JOIN Z_26ASSETS ON {album_join} = {asset_table}.Z_PK
JOIN ZGENERICALBUM ON ZGENERICALBUM.Z_PK = Z_26ASSETS.Z_26ALBUMS
"""
)
@@ -1668,11 +1648,11 @@ class PhotosDB:
# get details on keywords
c.execute(
"SELECT ZKEYWORD.ZTITLE, ZGENERICASSET.ZUUID "
"FROM ZGENERICASSET "
"JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK "
"JOIN Z_1KEYWORDS ON Z_1KEYWORDS.Z_1ASSETATTRIBUTES = ZADDITIONALASSETATTRIBUTES.Z_PK "
"JOIN ZKEYWORD ON ZKEYWORD.Z_PK = Z_1KEYWORDS.Z_37KEYWORDS "
f"""SELECT ZKEYWORD.ZTITLE, {asset_table}.ZUUID
FROM {asset_table}
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
JOIN Z_1KEYWORDS ON Z_1KEYWORDS.Z_1ASSETATTRIBUTES = ZADDITIONALASSETATTRIBUTES.Z_PK
JOIN ZKEYWORD ON ZKEYWORD.Z_PK = {keyword_join} """
)
for keyword in c:
if not keyword[1] in self._dbkeywords_uuid:
@@ -1699,45 +1679,45 @@ class PhotosDB:
# get details about photos
logging.debug(f"Getting information about photos")
c.execute(
"""SELECT ZGENERICASSET.ZUUID,
f"""SELECT {asset_table}.ZUUID,
ZADDITIONALASSETATTRIBUTES.ZMASTERFINGERPRINT,
ZADDITIONALASSETATTRIBUTES.ZTITLE,
ZADDITIONALASSETATTRIBUTES.ZORIGINALFILENAME,
ZGENERICASSET.ZMODIFICATIONDATE,
ZGENERICASSET.ZDATECREATED,
{asset_table}.ZMODIFICATIONDATE,
{asset_table}.ZDATECREATED,
ZADDITIONALASSETATTRIBUTES.ZTIMEZONEOFFSET,
ZADDITIONALASSETATTRIBUTES.ZINFERREDTIMEZONEOFFSET,
ZADDITIONALASSETATTRIBUTES.ZTIMEZONENAME,
ZGENERICASSET.ZHIDDEN,
ZGENERICASSET.ZFAVORITE,
ZGENERICASSET.ZDIRECTORY,
ZGENERICASSET.ZFILENAME,
ZGENERICASSET.ZLATITUDE,
ZGENERICASSET.ZLONGITUDE,
ZGENERICASSET.ZHASADJUSTMENTS,
ZGENERICASSET.ZCLOUDBATCHPUBLISHDATE,
ZGENERICASSET.ZKIND,
ZGENERICASSET.ZUNIFORMTYPEIDENTIFIER,
ZGENERICASSET.ZAVALANCHEUUID,
ZGENERICASSET.ZAVALANCHEPICKTYPE,
ZGENERICASSET.ZKINDSUBTYPE,
ZGENERICASSET.ZCUSTOMRENDEREDVALUE,
{asset_table}.ZHIDDEN,
{asset_table}.ZFAVORITE,
{asset_table}.ZDIRECTORY,
{asset_table}.ZFILENAME,
{asset_table}.ZLATITUDE,
{asset_table}.ZLONGITUDE,
{asset_table}.ZHASADJUSTMENTS,
{asset_table}.ZCLOUDBATCHPUBLISHDATE,
{asset_table}.ZKIND,
{asset_table}.ZUNIFORMTYPEIDENTIFIER,
{asset_table}.ZAVALANCHEUUID,
{asset_table}.ZAVALANCHEPICKTYPE,
{asset_table}.ZKINDSUBTYPE,
{asset_table}.ZCUSTOMRENDEREDVALUE,
ZADDITIONALASSETATTRIBUTES.ZCAMERACAPTUREDEVICE,
ZGENERICASSET.ZCLOUDASSETGUID,
{asset_table}.ZCLOUDASSETGUID,
ZADDITIONALASSETATTRIBUTES.ZREVERSELOCATIONDATA,
ZGENERICASSET.ZMOMENT,
{asset_table}.ZMOMENT,
ZADDITIONALASSETATTRIBUTES.ZORIGINALRESOURCECHOICE,
ZGENERICASSET.ZTRASHEDSTATE,
ZGENERICASSET.ZHEIGHT,
ZGENERICASSET.ZWIDTH,
ZGENERICASSET.ZORIENTATION,
{asset_table}.ZTRASHEDSTATE,
{asset_table}.ZHEIGHT,
{asset_table}.ZWIDTH,
{asset_table}.ZORIENTATION,
ZADDITIONALASSETATTRIBUTES.ZORIGINALHEIGHT,
ZADDITIONALASSETATTRIBUTES.ZORIGINALWIDTH,
ZADDITIONALASSETATTRIBUTES.ZORIGINALORIENTATION,
ZADDITIONALASSETATTRIBUTES.ZORIGINALFILESIZE
FROM ZGENERICASSET
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK
ORDER BY ZGENERICASSET.ZUUID """
FROM {asset_table}
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
ORDER BY {asset_table}.ZUUID """
)
# Order of results
# 0 SELECT ZGENERICASSET.ZUUID,
@@ -1973,12 +1953,12 @@ class PhotosDB:
# Get extended description
c.execute(
"SELECT ZGENERICASSET.ZUUID, "
"ZASSETDESCRIPTION.ZLONGDESCRIPTION "
"FROM ZGENERICASSET "
"JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK "
"JOIN ZASSETDESCRIPTION ON ZASSETDESCRIPTION.Z_PK = ZADDITIONALASSETATTRIBUTES.ZASSETDESCRIPTION "
"ORDER BY ZGENERICASSET.ZUUID "
f"""SELECT {asset_table}.ZUUID,
ZASSETDESCRIPTION.ZLONGDESCRIPTION
FROM {asset_table}
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
JOIN ZASSETDESCRIPTION ON ZASSETDESCRIPTION.Z_PK = ZADDITIONALASSETATTRIBUTES.ZASSETDESCRIPTION
ORDER BY {asset_table}.ZUUID """
)
for row in c:
uuid = row[0]
@@ -1992,12 +1972,12 @@ class PhotosDB:
# get information about adjusted/edited photos
c.execute(
"SELECT ZGENERICASSET.ZUUID, "
"ZGENERICASSET.ZHASADJUSTMENTS, "
"ZUNMANAGEDADJUSTMENT.ZADJUSTMENTFORMATIDENTIFIER "
"FROM ZGENERICASSET, ZUNMANAGEDADJUSTMENT "
"JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK "
"WHERE ZADDITIONALASSETATTRIBUTES.ZUNMANAGEDADJUSTMENT = ZUNMANAGEDADJUSTMENT.Z_PK "
f"""SELECT {asset_table}.ZUUID,
{asset_table}.ZHASADJUSTMENTS,
ZUNMANAGEDADJUSTMENT.ZADJUSTMENTFORMATIDENTIFIER
FROM {asset_table}, ZUNMANAGEDADJUSTMENT
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
WHERE ZADDITIONALASSETATTRIBUTES.ZUNMANAGEDADJUSTMENT = ZUNMANAGEDADJUSTMENT.Z_PK """
)
for row in c:
uuid = row[0]
@@ -2016,12 +1996,12 @@ class PhotosDB:
# Get info on remote/local availability for photos in shared albums
c.execute(
""" SELECT
ZGENERICASSET.ZUUID,
f""" SELECT
{asset_table}.ZUUID,
ZINTERNALRESOURCE.ZLOCALAVAILABILITY,
ZINTERNALRESOURCE.ZREMOTEAVAILABILITY
FROM ZGENERICASSET
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK
FROM {asset_table}
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
JOIN ZINTERNALRESOURCE ON ZINTERNALRESOURCE.ZASSET = ZADDITIONALASSETATTRIBUTES.ZASSET
WHERE ZDATASTORESUBTYPE = 1 OR ZDATASTORESUBTYPE = 3 """
)
@@ -2047,11 +2027,11 @@ class PhotosDB:
# get information on local/remote availability
c.execute(
""" SELECT ZGENERICASSET.ZUUID,
f""" SELECT {asset_table}.ZUUID,
ZINTERNALRESOURCE.ZLOCALAVAILABILITY,
ZINTERNALRESOURCE.ZREMOTEAVAILABILITY
FROM ZGENERICASSET
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK
FROM {asset_table}
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
JOIN ZINTERNALRESOURCE ON ZINTERNALRESOURCE.ZFINGERPRINT = ZADDITIONALASSETATTRIBUTES.ZMASTERFINGERPRINT """
)
@@ -2075,11 +2055,11 @@ class PhotosDB:
# get information about cloud sync state
c.execute(
""" SELECT
ZGENERICASSET.ZUUID,
f""" SELECT
{asset_table}.ZUUID,
ZCLOUDMASTER.ZCLOUDLOCALSTATE
FROM ZCLOUDMASTER, ZGENERICASSET
WHERE ZGENERICASSET.ZMASTER = ZCLOUDMASTER.Z_PK """
FROM ZCLOUDMASTER, {asset_table}
WHERE {asset_table}.ZMASTER = ZCLOUDMASTER.Z_PK """
)
for row in c:
uuid = row[0]
@@ -2090,15 +2070,15 @@ class PhotosDB:
# get information about associted RAW images
# RAW images have ZDATASTORESUBTYPE = 17
c.execute(
""" SELECT
ZGENERICASSET.ZUUID,
f""" SELECT
{asset_table}.ZUUID,
ZINTERNALRESOURCE.ZDATALENGTH,
ZUNIFORMTYPEIDENTIFIER.ZIDENTIFIER,
ZINTERNALRESOURCE.ZDATASTORESUBTYPE,
ZINTERNALRESOURCE.ZRESOURCETYPE
FROM ZGENERICASSET
FROM {asset_table}
JOIN ZINTERNALRESOURCE ON ZINTERNALRESOURCE.ZASSET = ZADDITIONALASSETATTRIBUTES.ZASSET
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
JOIN ZUNIFORMTYPEIDENTIFIER ON ZUNIFORMTYPEIDENTIFIER.Z_PK = ZINTERNALRESOURCE.ZUNIFORMTYPEIDENTIFIER
WHERE ZINTERNALRESOURCE.ZDATASTORESUBTYPE = 17
"""

View File

@@ -0,0 +1,84 @@
""" utility functions used by PhotosDB """
import logging
import plistlib
from .._constants import (
_PHOTOS_5_MODEL_VERSION,
_PHOTOS_6_MODEL_VERSION,
_TESTED_DB_VERSIONS,
)
from ..utils import _open_sql_file
def get_db_version(db_file):
""" Gets the Photos DB version from LiGlobals table
Args:
db_file: path to photos.db database file containing LiGlobals table
Returns: version as str
"""
version = None
(conn, c) = _open_sql_file(db_file)
# get database version
c.execute("SELECT value from LiGlobals where LiGlobals.keyPath is 'libraryVersion'")
version = c.fetchone()[0]
conn.close()
if version not in _TESTED_DB_VERSIONS:
print(
f"WARNING: Only tested on database versions [{', '.join(_TESTED_DB_VERSIONS)}]"
+ f" You have database version={version} which has not been tested"
)
return version
def get_model_version(db_file):
""" Returns the database model version from Z_METADATA
Args:
db_file: path to Photos.sqlite database file containing Z_METADATA table
Returns: model version as str
"""
version = None
(conn, c) = _open_sql_file(db_file)
# get database version
c.execute("SELECT MAX(Z_VERSION), Z_PLIST FROM Z_METADATA")
results = c.fetchone()
conn.close()
plist = plistlib.loads(results[1])
return plist["PLModelVersion"]
def get_db_model_version(db_file):
""" Returns Photos version based on model version found in db_file
Args:
db_file: path to Photos.sqlite file
Returns: int of major Photos version number (e.g. 5 or 6).
If unknown model version found, logs warning and returns most current Photos version.
"""
model_ver = get_model_version(db_file)
if _PHOTOS_5_MODEL_VERSION[0] <= model_ver <= _PHOTOS_5_MODEL_VERSION[1]:
db_ver = 5
elif _PHOTOS_6_MODEL_VERSION[0] <= model_ver <= _PHOTOS_6_MODEL_VERSION[1]:
db_ver = 6
else:
logging.warning(f"Unknown model version: {model_ver}")
# cross our fingers and try latest version
db_ver = 6
return db_ver