Added verbose to PhotosDB(), partial fix for #110
This commit is contained in:
@@ -521,10 +521,15 @@ def cli(ctx, db, json_, debug):
|
|||||||
help="Use with '--dump photos' to dump only certain UUIDs",
|
help="Use with '--dump photos' to dump only certain UUIDs",
|
||||||
multiple=True,
|
multiple=True,
|
||||||
)
|
)
|
||||||
|
@click.option("--verbose", "-V", "verbose_", is_flag=True, help="Print verbose output.")
|
||||||
@click.pass_obj
|
@click.pass_obj
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def debug_dump(ctx, cli_obj, db, photos_library, dump, uuid):
|
def debug_dump(ctx, cli_obj, db, photos_library, dump, uuid, verbose_):
|
||||||
""" Print out debug info """
|
""" Print out debug info """
|
||||||
|
|
||||||
|
global VERBOSE
|
||||||
|
VERBOSE = bool(verbose_)
|
||||||
|
|
||||||
db = get_photos_db(*photos_library, db, cli_obj.db)
|
db = get_photos_db(*photos_library, db, cli_obj.db)
|
||||||
if db is None:
|
if db is None:
|
||||||
click.echo(cli.commands["debug-dump"].get_help(ctx), err=True)
|
click.echo(cli.commands["debug-dump"].get_help(ctx), err=True)
|
||||||
@@ -534,7 +539,7 @@ def debug_dump(ctx, cli_obj, db, photos_library, dump, uuid):
|
|||||||
|
|
||||||
start_t = time.perf_counter()
|
start_t = time.perf_counter()
|
||||||
print(f"Opening database: {db}")
|
print(f"Opening database: {db}")
|
||||||
photosdb = osxphotos.PhotosDB(dbfile=db)
|
photosdb = osxphotos.PhotosDB(dbfile=db, verbose=verbose)
|
||||||
stop_t = time.perf_counter()
|
stop_t = time.perf_counter()
|
||||||
print(f"Done; took {(stop_t-start_t):.2f} seconds")
|
print(f"Done; took {(stop_t-start_t):.2f} seconds")
|
||||||
|
|
||||||
@@ -1900,7 +1905,7 @@ def _query(
|
|||||||
arguments must be passed in same order as query and export
|
arguments must be passed in same order as query and export
|
||||||
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=db)
|
photosdb = osxphotos.PhotosDB(dbfile=db, verbose=verbose)
|
||||||
if deleted or deleted_only:
|
if deleted or deleted_only:
|
||||||
photos = photosdb.photos(
|
photos = photosdb.photos(
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.35.8"
|
__version__ = "0.35.9"
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ from ..utils import (
|
|||||||
_get_os_version,
|
_get_os_version,
|
||||||
_open_sql_file,
|
_open_sql_file,
|
||||||
get_last_library_path,
|
get_last_library_path,
|
||||||
|
noop,
|
||||||
normalize_unicode,
|
normalize_unicode,
|
||||||
)
|
)
|
||||||
from .photosdb_utils import get_db_model_version, get_db_version
|
from .photosdb_utils import get_db_model_version, get_db_version
|
||||||
@@ -68,11 +69,16 @@ class PhotosDB:
|
|||||||
)
|
)
|
||||||
from ._photosdb_process_scoreinfo import _process_scoreinfo
|
from ._photosdb_process_scoreinfo import _process_scoreinfo
|
||||||
|
|
||||||
def __init__(self, dbfile=None):
|
def __init__(self, dbfile=None, verbose=None):
|
||||||
""" Create a new PhotosDB object.
|
""" Create a new PhotosDB object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
dbfile: specify full path to photos library or photos.db; if None, will attempt to locate last library opened by Photos.
|
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.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError if dbfile is not a valid Photos library.
|
||||||
|
TypeError if verbose is not None and not callable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check OS version
|
# Check OS version
|
||||||
@@ -85,6 +91,12 @@ class PhotosDB:
|
|||||||
f"you have {system}, OS version: {major}"
|
f"you have {system}, OS version: {major}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if verbose is None:
|
||||||
|
verbose = noop
|
||||||
|
elif not callable(verbose):
|
||||||
|
raise TypeError("verbose must be callable")
|
||||||
|
self._verbose = verbose
|
||||||
|
|
||||||
# create a temporary directory
|
# create a temporary directory
|
||||||
# tempfile.TemporaryDirectory gets cleaned up when the object does
|
# tempfile.TemporaryDirectory gets cleaned up when the object does
|
||||||
self._tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
|
self._tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
|
||||||
@@ -245,11 +257,14 @@ class PhotosDB:
|
|||||||
# or photosanalysisd
|
# or photosanalysisd
|
||||||
self._dbfile = self._dbfile_actual = self._tmp_db = os.path.abspath(dbfile)
|
self._dbfile = self._dbfile_actual = self._tmp_db = os.path.abspath(dbfile)
|
||||||
|
|
||||||
|
verbose(f"Processing database {self._dbfile}")
|
||||||
|
|
||||||
# if database is exclusively locked, make a copy of it and use the copy
|
# if database is exclusively locked, make a copy of it and use the copy
|
||||||
# Photos maintains an exclusive lock on the database file while Photos is open
|
# Photos maintains an exclusive lock on the database file while Photos is open
|
||||||
# photoanalysisd sometimes maintains this lock even after Photos is closed
|
# photoanalysisd sometimes maintains this lock even after Photos is closed
|
||||||
# In those cases, make a temp copy of the file for sqlite3 to read
|
# In those cases, make a temp copy of the file for sqlite3 to read
|
||||||
if _db_is_locked(self._dbfile):
|
if _db_is_locked(self._dbfile):
|
||||||
|
verbose(f"Database locked, creating temporary copy.")
|
||||||
self._tmp_db = self._copy_db_file(self._dbfile)
|
self._tmp_db = self._copy_db_file(self._dbfile)
|
||||||
|
|
||||||
self._db_version = get_db_version(self._tmp_db)
|
self._db_version = get_db_version(self._tmp_db)
|
||||||
@@ -262,8 +277,10 @@ class PhotosDB:
|
|||||||
raise FileNotFoundError(f"dbfile {dbfile} does not exist", dbfile)
|
raise FileNotFoundError(f"dbfile {dbfile} does not exist", dbfile)
|
||||||
else:
|
else:
|
||||||
self._dbfile_actual = self._tmp_db = dbfile
|
self._dbfile_actual = self._tmp_db = dbfile
|
||||||
|
verbose(f"Processing database {self._dbfile_actual}")
|
||||||
# if database is exclusively locked, make a copy of it and use the copy
|
# if database is exclusively locked, make a copy of it and use the copy
|
||||||
if _db_is_locked(self._dbfile_actual):
|
if _db_is_locked(self._dbfile_actual):
|
||||||
|
verbose(f"Database locked, creating temporary copy.")
|
||||||
self._tmp_db = self._copy_db_file(self._dbfile_actual)
|
self._tmp_db = self._copy_db_file(self._dbfile_actual)
|
||||||
|
|
||||||
if _debug():
|
if _debug():
|
||||||
@@ -532,10 +549,15 @@ class PhotosDB:
|
|||||||
""" process the Photos database to extract info
|
""" process the Photos database to extract info
|
||||||
works on Photos version <= 4.0 """
|
works on Photos version <= 4.0 """
|
||||||
|
|
||||||
|
verbose = self._verbose
|
||||||
|
verbose("Processing database.")
|
||||||
|
verbose(f"Database version: {self._db_version}.")
|
||||||
|
|
||||||
(conn, c) = _open_sql_file(self._tmp_db)
|
(conn, c) = _open_sql_file(self._tmp_db)
|
||||||
|
|
||||||
# get info to associate persons with photos
|
# get info to associate persons with photos
|
||||||
# then get detected faces in each photo and link to persons
|
# then get detected faces in each photo and link to persons
|
||||||
|
verbose("Processing persons in photos.")
|
||||||
c.execute(
|
c.execute(
|
||||||
""" SELECT
|
""" SELECT
|
||||||
RKPerson.modelID,
|
RKPerson.modelID,
|
||||||
@@ -601,6 +623,7 @@ class PhotosDB:
|
|||||||
logging.debug(f"Unexpected KeyError _dbpersons_pk[{pk}]")
|
logging.debug(f"Unexpected KeyError _dbpersons_pk[{pk}]")
|
||||||
|
|
||||||
# get information on detected faces
|
# get information on detected faces
|
||||||
|
verbose("Processing detected faces in photos.")
|
||||||
c.execute(
|
c.execute(
|
||||||
""" SELECT
|
""" SELECT
|
||||||
RKPerson.modelID,
|
RKPerson.modelID,
|
||||||
@@ -638,6 +661,7 @@ class PhotosDB:
|
|||||||
logging.debug(pformat(self._dbfaces_uuid))
|
logging.debug(pformat(self._dbfaces_uuid))
|
||||||
|
|
||||||
# Get info on albums
|
# Get info on albums
|
||||||
|
verbose("Processing albums.")
|
||||||
c.execute(
|
c.execute(
|
||||||
""" SELECT
|
""" SELECT
|
||||||
RKAlbum.uuid,
|
RKAlbum.uuid,
|
||||||
@@ -780,6 +804,7 @@ class PhotosDB:
|
|||||||
logging.debug(pformat(self._dbfolder_details))
|
logging.debug(pformat(self._dbfolder_details))
|
||||||
|
|
||||||
# Get info on keywords
|
# Get info on keywords
|
||||||
|
verbose("Processing keywords.")
|
||||||
c.execute(
|
c.execute(
|
||||||
""" SELECT
|
""" SELECT
|
||||||
RKKeyword.name,
|
RKKeyword.name,
|
||||||
@@ -807,6 +832,7 @@ class PhotosDB:
|
|||||||
self._dbvolumes[vol[0]] = vol[1]
|
self._dbvolumes[vol[0]] = vol[1]
|
||||||
|
|
||||||
# Get photo details
|
# Get photo details
|
||||||
|
verbose("Processing photo details.")
|
||||||
if self._db_version < _PHOTOS_3_VERSION:
|
if self._db_version < _PHOTOS_3_VERSION:
|
||||||
# Photos < 3.0 doesn't have RKVersion.selfPortrait (selfie)
|
# Photos < 3.0 doesn't have RKVersion.selfPortrait (selfie)
|
||||||
c.execute(
|
c.execute(
|
||||||
@@ -1096,6 +1122,7 @@ class PhotosDB:
|
|||||||
self._dbphotos[uuid]["fok_import_session"] = None
|
self._dbphotos[uuid]["fok_import_session"] = None
|
||||||
|
|
||||||
# get additional details from RKMaster, needed for RAW processing
|
# get additional details from RKMaster, needed for RAW processing
|
||||||
|
verbose("Processing additional photo details.")
|
||||||
c.execute(
|
c.execute(
|
||||||
""" SELECT
|
""" SELECT
|
||||||
RKMaster.uuid,
|
RKMaster.uuid,
|
||||||
@@ -1269,6 +1296,7 @@ class PhotosDB:
|
|||||||
self._dbphotos[uuid]["incloud"] = True if row[2] == 1 else False
|
self._dbphotos[uuid]["incloud"] = True if row[2] == 1 else False
|
||||||
|
|
||||||
# get location data
|
# get location data
|
||||||
|
verbose("Processing location data.")
|
||||||
# get the country codes
|
# get the country codes
|
||||||
country_codes = c.execute(
|
country_codes = c.execute(
|
||||||
"SELECT modelID, countryCode "
|
"SELECT modelID, countryCode "
|
||||||
@@ -1355,6 +1383,7 @@ class PhotosDB:
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
# process faces
|
# process faces
|
||||||
|
verbose("Processing face details.")
|
||||||
self._process_faceinfo()
|
self._process_faceinfo()
|
||||||
|
|
||||||
# add faces and keywords to photo data
|
# add faces and keywords to photo data
|
||||||
@@ -1391,6 +1420,7 @@ class PhotosDB:
|
|||||||
self._dbphotos[uuid]["volume"] = None
|
self._dbphotos[uuid]["volume"] = None
|
||||||
|
|
||||||
# done processing, dump debug data if requested
|
# done processing, dump debug data if requested
|
||||||
|
verbose("Done processing details from Photos library.")
|
||||||
if _debug():
|
if _debug():
|
||||||
logging.debug("Faces (_dbfaces_uuid):")
|
logging.debug("Faces (_dbfaces_uuid):")
|
||||||
logging.debug(pformat(self._dbfaces_uuid))
|
logging.debug(pformat(self._dbfaces_uuid))
|
||||||
@@ -1466,12 +1496,14 @@ class PhotosDB:
|
|||||||
|
|
||||||
if _debug():
|
if _debug():
|
||||||
logging.debug(f"_process_database5")
|
logging.debug(f"_process_database5")
|
||||||
|
verbose = self._verbose
|
||||||
|
verbose(f"Processing database.")
|
||||||
(conn, c) = _open_sql_file(self._tmp_db)
|
(conn, c) = _open_sql_file(self._tmp_db)
|
||||||
|
|
||||||
# some of the tables/columns have different names in different versions of Photos
|
# some of the tables/columns have different names in different versions of Photos
|
||||||
photos_ver = get_db_model_version(self._tmp_db)
|
photos_ver = get_db_model_version(self._tmp_db)
|
||||||
self._photos_ver = photos_ver
|
self._photos_ver = photos_ver
|
||||||
|
verbose(f"Database version: {self._db_version}, {photos_ver}.")
|
||||||
asset_table = _DB_TABLE_NAMES[photos_ver]["ASSET"]
|
asset_table = _DB_TABLE_NAMES[photos_ver]["ASSET"]
|
||||||
keyword_join = _DB_TABLE_NAMES[photos_ver]["KEYWORD_JOIN"]
|
keyword_join = _DB_TABLE_NAMES[photos_ver]["KEYWORD_JOIN"]
|
||||||
album_join = _DB_TABLE_NAMES[photos_ver]["ALBUM_JOIN"]
|
album_join = _DB_TABLE_NAMES[photos_ver]["ALBUM_JOIN"]
|
||||||
@@ -1485,6 +1517,7 @@ class PhotosDB:
|
|||||||
|
|
||||||
# get info to associate persons with photos
|
# get info to associate persons with photos
|
||||||
# then get detected faces in each photo and link to persons
|
# then get detected faces in each photo and link to persons
|
||||||
|
verbose("Processing persons in photos.")
|
||||||
c.execute(
|
c.execute(
|
||||||
""" SELECT
|
""" SELECT
|
||||||
ZPERSON.Z_PK,
|
ZPERSON.Z_PK,
|
||||||
@@ -1550,6 +1583,7 @@ class PhotosDB:
|
|||||||
logging.debug(f"Unexpected KeyError _dbpersons_pk[{pk}]")
|
logging.debug(f"Unexpected KeyError _dbpersons_pk[{pk}]")
|
||||||
|
|
||||||
# get information on detected faces
|
# get information on detected faces
|
||||||
|
verbose("Processing detected faces in photos.")
|
||||||
c.execute(
|
c.execute(
|
||||||
f""" SELECT
|
f""" SELECT
|
||||||
ZPERSON.Z_PK,
|
ZPERSON.Z_PK,
|
||||||
@@ -1584,6 +1618,7 @@ class PhotosDB:
|
|||||||
logging.debug(pformat(self._dbfaces_uuid))
|
logging.debug(pformat(self._dbfaces_uuid))
|
||||||
|
|
||||||
# get details about albums
|
# get details about albums
|
||||||
|
verbose("Processing albums.")
|
||||||
c.execute(
|
c.execute(
|
||||||
f""" SELECT
|
f""" SELECT
|
||||||
ZGENERICALBUM.ZUUID,
|
ZGENERICALBUM.ZUUID,
|
||||||
@@ -1702,6 +1737,7 @@ class PhotosDB:
|
|||||||
logging.debug(pformat(self._dbalbum_folders))
|
logging.debug(pformat(self._dbalbum_folders))
|
||||||
|
|
||||||
# get details on keywords
|
# get details on keywords
|
||||||
|
verbose("Processing keywords.")
|
||||||
c.execute(
|
c.execute(
|
||||||
f"""SELECT ZKEYWORD.ZTITLE, {asset_table}.ZUUID
|
f"""SELECT ZKEYWORD.ZTITLE, {asset_table}.ZUUID
|
||||||
FROM {asset_table}
|
FROM {asset_table}
|
||||||
@@ -1733,6 +1769,7 @@ class PhotosDB:
|
|||||||
logging.debug(self._dbvolumes)
|
logging.debug(self._dbvolumes)
|
||||||
|
|
||||||
# get details about photos
|
# get details about photos
|
||||||
|
verbose("Processing photo details.")
|
||||||
logging.debug(f"Getting information about photos")
|
logging.debug(f"Getting information about photos")
|
||||||
c.execute(
|
c.execute(
|
||||||
f"""SELECT {asset_table}.ZUUID,
|
f"""SELECT {asset_table}.ZUUID,
|
||||||
@@ -1891,7 +1928,7 @@ class PhotosDB:
|
|||||||
info["type"] = None
|
info["type"] = None
|
||||||
|
|
||||||
info["UTI"] = row[18]
|
info["UTI"] = row[18]
|
||||||
info["UTI_original"] = None # filled in later
|
info["UTI_original"] = None # filled in later
|
||||||
|
|
||||||
# handle burst photos
|
# handle burst photos
|
||||||
# if burst photo, determine whether or not it's a selected burst photo
|
# if burst photo, determine whether or not it's a selected burst photo
|
||||||
@@ -2023,6 +2060,7 @@ class PhotosDB:
|
|||||||
# 1 ZGENERICASSET.ZIMPORTSESSION
|
# 1 ZGENERICASSET.ZIMPORTSESSION
|
||||||
# 2 ZGENERICASSET.Z_FOK_IMPORTSESSION
|
# 2 ZGENERICASSET.Z_FOK_IMPORTSESSION
|
||||||
# 3 ZGENERICALBUM.ZUUID,
|
# 3 ZGENERICALBUM.ZUUID,
|
||||||
|
verbose("Processing import sessions.")
|
||||||
c.execute(
|
c.execute(
|
||||||
f"""SELECT
|
f"""SELECT
|
||||||
{asset_table}.ZUUID,
|
{asset_table}.ZUUID,
|
||||||
@@ -2045,6 +2083,7 @@ class PhotosDB:
|
|||||||
logging.debug(f"No info record for uuid {uuid} for import session")
|
logging.debug(f"No info record for uuid {uuid} for import session")
|
||||||
|
|
||||||
# Get extended description
|
# Get extended description
|
||||||
|
verbose("Processing additional photo details.")
|
||||||
c.execute(
|
c.execute(
|
||||||
f"""SELECT {asset_table}.ZUUID,
|
f"""SELECT {asset_table}.ZUUID,
|
||||||
ZASSETDESCRIPTION.ZLONGDESCRIPTION
|
ZASSETDESCRIPTION.ZLONGDESCRIPTION
|
||||||
@@ -2224,18 +2263,23 @@ class PhotosDB:
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
# process face info
|
# process face info
|
||||||
|
verbose("Processing face details.")
|
||||||
self._process_faceinfo()
|
self._process_faceinfo()
|
||||||
|
|
||||||
# process search info
|
# process search info
|
||||||
|
verbose("Processing photo labels.")
|
||||||
self._process_searchinfo()
|
self._process_searchinfo()
|
||||||
|
|
||||||
# process exif info
|
# process exif info
|
||||||
|
verbose("Processing EXIF details.")
|
||||||
self._process_exifinfo()
|
self._process_exifinfo()
|
||||||
|
|
||||||
# process computed scores
|
# process computed scores
|
||||||
|
verbose("Processing computed aesthetic scores.")
|
||||||
self._process_scoreinfo()
|
self._process_scoreinfo()
|
||||||
|
|
||||||
# done processing, dump debug data if requested
|
# done processing, dump debug data if requested
|
||||||
|
verbose("Done processing details from Photos library.")
|
||||||
if _debug():
|
if _debug():
|
||||||
logging.debug("Faces (_dbfaces_uuid):")
|
logging.debug("Faces (_dbfaces_uuid):")
|
||||||
logging.debug(pformat(self._dbfaces_uuid))
|
logging.debug(pformat(self._dbfaces_uuid))
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ def _debug():
|
|||||||
""" returns True if debugging turned on (via _set_debug), otherwise, false """
|
""" returns True if debugging turned on (via _set_debug), otherwise, false """
|
||||||
return _DEBUG
|
return _DEBUG
|
||||||
|
|
||||||
|
def noop(*args, **kwargs):
|
||||||
|
""" do nothing (no operation) """
|
||||||
|
pass
|
||||||
|
|
||||||
def _get_os_version():
|
def _get_os_version():
|
||||||
# returns tuple containing OS version
|
# returns tuple containing OS version
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ import os
|
|||||||
import pytest
|
import pytest
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
import osxphotos
|
||||||
from osxphotos.exiftool import get_exiftool_path
|
from osxphotos.exiftool import get_exiftool_path
|
||||||
|
|
||||||
CLI_PHOTOS_DB = "tests/Test-10.15.1.photoslibrary"
|
CLI_PHOTOS_DB = "tests/Test-10.15.1.photoslibrary"
|
||||||
@@ -419,6 +420,13 @@ CLI_EXPORT_UUID_FROM_FILE_FILENAMES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def reset_globals():
|
||||||
|
""" reset globals in __main__ that tests may have changed """
|
||||||
|
yield
|
||||||
|
osxphotos.__main__.VERBOSE = False
|
||||||
|
|
||||||
|
|
||||||
# determine if exiftool installed so exiftool tests can be skipped
|
# determine if exiftool installed so exiftool tests can be skipped
|
||||||
try:
|
try:
|
||||||
exiftool = get_exiftool_path()
|
exiftool = get_exiftool_path()
|
||||||
|
|||||||
Reference in New Issue
Block a user