Bug labels ventura 816 (#823)

* Partial fix for #816, labels on Ventura / macOS 13.0

* Version bump
This commit is contained in:
Rhet Turnbull 2022-11-12 11:22:44 -08:00 committed by GitHub
parent 6dbeaae541
commit ae560d24cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 125 additions and 113 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.52.0
current_version = 0.53.0
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
serialize = {major}.{minor}.{patch}

View File

@ -2012,7 +2012,7 @@ cog.out(get_template_field_table())
|{cr}|A carriage return: '\r'|
|{crlf}|A carriage return + line feed: '\r\n'|
|{tab}|:A tab: '\t'|
|{osxphotos_version}|The osxphotos version, e.g. '0.52.0'|
|{osxphotos_version}|The osxphotos version, e.g. '0.53.0'|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|{album}|Album(s) photo is contained in|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|

View File

@ -2009,7 +2009,7 @@ Substitution Description
{cr} A carriage return: '\r'
{crlf} A carriage return + line feed: '\r\n'
{tab} :A tab: '\t'
{osxphotos_version} The osxphotos version, e.g. '0.52.0'
{osxphotos_version} The osxphotos version, e.g. '0.53.0'
{osxphotos_cmd_line} The full command line used to run osxphotos
The following substitutions may result in multiple values. Thus if specified
@ -2493,7 +2493,7 @@ The following template field substitutions are availabe for use the templating s
|{cr}|A carriage return: '\r'|
|{crlf}|A carriage return + line feed: '\r\n'|
|{tab}|:A tab: '\t'|
|{osxphotos_version}|The osxphotos version, e.g. '0.52.0'|
|{osxphotos_version}|The osxphotos version, e.g. '0.53.0'|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|{album}|Album(s) photo is contained in|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|

View File

@ -171,64 +171,91 @@ _MAX_IPTC_KEYWORD_LEN = 64
# If anyone has a keyword matching this, then too bad...
_OSXPHOTOS_NONE_SENTINEL = "OSXPhotosXYZZY42_Sentinel$"
# SearchInfo categories for Photos 5, corresponds to categories in database/search/psi.sqlite
SEARCH_CATEGORY_LABEL = 2024
SEARCH_CATEGORY_PLACE_NAME = 1
SEARCH_CATEGORY_STREET = 2
SEARCH_CATEGORY_NEIGHBORHOOD = 3
SEARCH_CATEGORY_LOCALITY_4 = 4
SEARCH_CATEGORY_SUB_LOCALITY_5 = 5
SEARCH_CATEGORY_SUB_LOCALITY_6 = 6
SEARCH_CATEGORY_CITY = 7
SEARCH_CATEGORY_LOCALITY_8 = 8
SEARCH_CATEGORY_NAMED_AREA = 9
SEARCH_CATEGORY_ALL_LOCALITY = [
SEARCH_CATEGORY_LOCALITY_4,
SEARCH_CATEGORY_SUB_LOCALITY_5,
SEARCH_CATEGORY_SUB_LOCALITY_6,
SEARCH_CATEGORY_LOCALITY_8,
SEARCH_CATEGORY_NAMED_AREA,
]
SEARCH_CATEGORY_STATE = 10
SEARCH_CATEGORY_STATE_ABBREVIATION = 11
SEARCH_CATEGORY_COUNTRY = 12
SEARCH_CATEGORY_BODY_OF_WATER = 14
SEARCH_CATEGORY_MONTH = 1014
SEARCH_CATEGORY_YEAR = 1015
SEARCH_CATEGORY_KEYWORDS = 2016
SEARCH_CATEGORY_TITLE = 2017
SEARCH_CATEGORY_DESCRIPTION = 2018
SEARCH_CATEGORY_HOME = 2020
SEARCH_CATEGORY_PERSON = 2021
SEARCH_CATEGORY_ACTIVITY = 2027
SEARCH_CATEGORY_HOLIDAY = 2029
SEARCH_CATEGORY_SEASON = 2030
SEARCH_CATEGORY_WORK = 2036
SEARCH_CATEGORY_VENUE = 2038
SEARCH_CATEGORY_VENUE_TYPE = 2039
SEARCH_CATEGORY_PHOTO_TYPE_VIDEO = 2044
SEARCH_CATEGORY_PHOTO_TYPE_SLOMO = 2045
SEARCH_CATEGORY_PHOTO_TYPE_LIVE = 2046
SEARCH_CATEGORY_PHOTO_TYPE_SCREENSHOT = 2047
SEARCH_CATEGORY_PHOTO_TYPE_PANORAMA = 2048
SEARCH_CATEGORY_PHOTO_TYPE_TIMELAPSE = 2049
SEARCH_CATEGORY_PHOTO_TYPE_BURSTS = 2052
SEARCH_CATEGORY_PHOTO_TYPE_PORTRAIT = 2053
SEARCH_CATEGORY_PHOTO_TYPE_SELFIES = 2054
SEARCH_CATEGORY_PHOTO_TYPE_FAVORITES = 2055
SEARCH_CATEGORY_MEDIA_TYPES = [
SEARCH_CATEGORY_PHOTO_TYPE_VIDEO,
SEARCH_CATEGORY_PHOTO_TYPE_SLOMO,
SEARCH_CATEGORY_PHOTO_TYPE_LIVE,
SEARCH_CATEGORY_PHOTO_TYPE_SCREENSHOT,
SEARCH_CATEGORY_PHOTO_TYPE_PANORAMA,
SEARCH_CATEGORY_PHOTO_TYPE_TIMELAPSE,
SEARCH_CATEGORY_PHOTO_TYPE_BURSTS,
SEARCH_CATEGORY_PHOTO_TYPE_PORTRAIT,
SEARCH_CATEGORY_PHOTO_TYPE_SELFIES,
SEARCH_CATEGORY_PHOTO_TYPE_FAVORITES,
]
SEARCH_CATEGORY_PHOTO_NAME = 2056
class SearchCategory:
"""SearchInfo categories for Photos 5+; corresponds to categories in database/search/psi.sqlite:groups.category
Note: This is a simple enum class; the values are not meant to be changed.
Would be great if Python enums actually let you access the value directly.
"""
LABEL = 2024
PLACE_NAME = 1
STREET = 2
NEIGHBORHOOD = 3
LOCALITY_4 = 4
SUB_LOCALITY_5 = 5
SUB_LOCALITY_6 = 6
CITY = 7
LOCALITY_8 = 8
NAMED_AREA = 9
ALL_LOCALITY = [
LOCALITY_4,
SUB_LOCALITY_5,
SUB_LOCALITY_6,
LOCALITY_8,
NAMED_AREA,
]
STATE = 10
STATE_ABBREVIATION = 11
COUNTRY = 12
BODY_OF_WATER = 14
MONTH = 1014
YEAR = 1015
KEYWORDS = 2016
TITLE = 2017
DESCRIPTION = 2018
HOME = 2020
PERSON = 2021
ACTIVITY = 2027
HOLIDAY = 2029
SEASON = 2030
WORK = 2036
VENUE = 2038
VENUE_TYPE = 2039
PHOTO_TYPE_VIDEO = 2044
PHOTO_TYPE_SLOMO = 2045
PHOTO_TYPE_LIVE = 2046
PHOTO_TYPE_SCREENSHOT = 2047
PHOTO_TYPE_PANORAMA = 2048
PHOTO_TYPE_TIMELAPSE = 2049
PHOTO_TYPE_BURSTS = 2052
PHOTO_TYPE_PORTRAIT = 2053
PHOTO_TYPE_SELFIES = 2054
PHOTO_TYPE_FAVORITES = 2055
MEDIA_TYPES = [
PHOTO_TYPE_VIDEO,
PHOTO_TYPE_SLOMO,
PHOTO_TYPE_LIVE,
PHOTO_TYPE_SCREENSHOT,
PHOTO_TYPE_PANORAMA,
PHOTO_TYPE_TIMELAPSE,
PHOTO_TYPE_BURSTS,
PHOTO_TYPE_PORTRAIT,
PHOTO_TYPE_SELFIES,
PHOTO_TYPE_FAVORITES,
]
PHOTO_NAME = 2056
class SearchCategory_Photos8(SearchCategory):
"""Search categories for Photos 8"""
# NOTE: This list is incomplete;
# until I get a test library that's been processed by photoanalysisd on Ventura,
# I can't verify all these are correct
LABEL = 1500
MONTH = 1100
YEAR = 1101
SEASON = 1104
ACTIVITY = 1600
KEYWORDS = 1200
PHOTO_NAME = 2100
def search_category_factory(version: int) -> SearchCategory:
"""Return SearchCategory class for Photos version"""
return SearchCategory_Photos8 if version >= 8 else SearchCategory
# Max filename length on MacOS

View File

@ -1,3 +1,3 @@
""" version info """
__version__ = "0.52.0"
__version__ = "0.53.0"

View File

@ -9,8 +9,8 @@ import uuid as uuidlib
from functools import lru_cache
from pprint import pformat
from .._constants import _PHOTOS_4_VERSION, SEARCH_CATEGORY_LABEL
from ..sqlite_utils import sqlite_open_ro, sqlite_db_is_locked
from .._constants import _PHOTOS_4_VERSION, search_category_factory
from ..sqlite_utils import sqlite_db_is_locked, sqlite_open_ro
from ..utils import normalize_unicode
"""
@ -134,7 +134,8 @@ def _process_searchinfo(self):
except KeyError:
_db_searchinfo_categories[category] = [record["normalized_string"]]
if category == SEARCH_CATEGORY_LABEL:
categories = search_category_factory(self._photos_ver)
if category == categories.LABEL:
label = record["content_string"]
label_norm = record["normalized_string"]
try:

View File

@ -57,7 +57,7 @@ from ..photoinfo import PhotoInfo
from ..phototemplate import RenderOptions
from ..queryoptions import QueryOptions
from ..rich_utils import add_rich_markup_tag
from ..sqlite_utils import sqlite_open_ro, sqlite_db_is_locked
from ..sqlite_utils import sqlite_db_is_locked, sqlite_open_ro
from ..utils import (
_check_file_exists,
_get_os_version,
@ -321,7 +321,10 @@ class PhotosDB:
verbose(f"Database locked, creating temporary copy.")
self._tmp_db = self._copy_db_file(self._dbfile)
# _db_version is set from photos.db
self._db_version = get_db_version(self._tmp_db)
# _photos_version is set from Photos.sqlite which only exists for Photos 5+
self._photos_ver = 4 if self._db_version == 4 else 5
# If Photos >= 5, actual data isn't in photos.db but in Photos.sqlite
if int(self._db_version) > int(_PHOTOS_4_VERSION):
@ -336,6 +339,8 @@ class PhotosDB:
if sqlite_db_is_locked(self._dbfile_actual):
verbose(f"Database locked, creating temporary copy.")
self._tmp_db = self._copy_db_file(self._dbfile_actual)
# set the photos version to actual value based on Photos.sqlite
self._photos_ver = get_db_model_version(self._tmp_db)
if is_debug():
logging.debug(
@ -588,7 +593,7 @@ class PhotosDB:
"""
return sqlite_open_ro(self._tmp_db)
def _copy_db_file(self, fname):
def _copy_db_file(self, fname: str) -> str:
"""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 """
@ -648,8 +653,6 @@ class PhotosDB:
verbose("Processing database.")
verbose(f"Database version: {self._num(self._db_version)}.")
self._photos_ver = 4 # only used in Photos 5+
(conn, c) = sqlite_open_ro(self._tmp_db)
# get info to associate persons with photos
@ -1604,8 +1607,8 @@ class PhotosDB:
(conn, c) = sqlite_open_ro(self._tmp_db)
# some of the tables/columns have different names in different versions of Photos
photos_ver = get_db_model_version(self._tmp_db)
self._photos_ver = photos_ver
# set local var for readability
photos_ver = self._photos_ver
verbose(
f"Database version: {self._num(self._db_version)}, {self._num(photos_ver)}."
)

View File

@ -69,7 +69,7 @@ def get_db_version(db_file):
return version
def get_model_version(db_file):
def get_model_version(db_file: str) -> str:
"""Returns the database model version from Z_METADATA
Args:
@ -90,7 +90,7 @@ def get_model_version(db_file):
return plist["PLModelVersion"]
def get_db_model_version(db_file):
def get_db_model_version(db_file: str) -> int:
"""Returns Photos version based on model version found in db_file
Args:

View File

@ -1,27 +1,7 @@
""" class for PhotoInfo exposing SearchInfo data such as labels
"""
from ._constants import (
_PHOTOS_4_VERSION,
SEARCH_CATEGORY_ACTIVITY,
SEARCH_CATEGORY_ALL_LOCALITY,
SEARCH_CATEGORY_BODY_OF_WATER,
SEARCH_CATEGORY_CITY,
SEARCH_CATEGORY_COUNTRY,
SEARCH_CATEGORY_HOLIDAY,
SEARCH_CATEGORY_LABEL,
SEARCH_CATEGORY_MEDIA_TYPES,
SEARCH_CATEGORY_MONTH,
SEARCH_CATEGORY_NEIGHBORHOOD,
SEARCH_CATEGORY_PLACE_NAME,
SEARCH_CATEGORY_SEASON,
SEARCH_CATEGORY_STATE,
SEARCH_CATEGORY_STATE_ABBREVIATION,
SEARCH_CATEGORY_STREET,
SEARCH_CATEGORY_VENUE,
SEARCH_CATEGORY_VENUE_TYPE,
SEARCH_CATEGORY_YEAR,
)
from ._constants import _PHOTOS_4_VERSION, search_category_factory
__all__ = ["SearchInfo"]
@ -38,6 +18,7 @@ class SearchInfo:
"search info not implemented for this database version"
)
self._categories = search_category_factory(photo._db._photos_ver)
self._photo = photo
self._normalized = normalized
self.uuid = photo.uuid
@ -51,103 +32,103 @@ class SearchInfo:
@property
def labels(self):
"""return list of labels associated with Photo"""
return self._get_text_for_category(SEARCH_CATEGORY_LABEL)
return self._get_text_for_category(self._categories.LABEL)
@property
def place_names(self):
"""returns list of place names"""
return self._get_text_for_category(SEARCH_CATEGORY_PLACE_NAME)
return self._get_text_for_category(self._categories.PLACE_NAME)
@property
def streets(self):
"""returns list of street names"""
return self._get_text_for_category(SEARCH_CATEGORY_STREET)
return self._get_text_for_category(self._categories.STREET)
@property
def neighborhoods(self):
"""returns list of neighborhoods"""
return self._get_text_for_category(SEARCH_CATEGORY_NEIGHBORHOOD)
return self._get_text_for_category(self._categories.NEIGHBORHOOD)
@property
def locality_names(self):
"""returns list of other locality names"""
locality = []
for category in SEARCH_CATEGORY_ALL_LOCALITY:
for category in self._categories.ALL_LOCALITY:
locality += self._get_text_for_category(category)
return locality
@property
def city(self):
"""returns city/town"""
city = self._get_text_for_category(SEARCH_CATEGORY_CITY)
city = self._get_text_for_category(self._categories.CITY)
return city[0] if city else ""
@property
def state(self):
"""returns state name"""
state = self._get_text_for_category(SEARCH_CATEGORY_STATE)
state = self._get_text_for_category(self._categories.STATE)
return state[0] if state else ""
@property
def state_abbreviation(self):
"""returns state abbreviation"""
abbrev = self._get_text_for_category(SEARCH_CATEGORY_STATE_ABBREVIATION)
abbrev = self._get_text_for_category(self._categories.STATE_ABBREVIATION)
return abbrev[0] if abbrev else ""
@property
def country(self):
"""returns country name"""
country = self._get_text_for_category(SEARCH_CATEGORY_COUNTRY)
country = self._get_text_for_category(self._categories.COUNTRY)
return country[0] if country else ""
@property
def month(self):
"""returns month name"""
month = self._get_text_for_category(SEARCH_CATEGORY_MONTH)
month = self._get_text_for_category(self._categories.MONTH)
return month[0] if month else ""
@property
def year(self):
"""returns year"""
year = self._get_text_for_category(SEARCH_CATEGORY_YEAR)
year = self._get_text_for_category(self._categories.YEAR)
return year[0] if year else ""
@property
def bodies_of_water(self):
"""returns list of body of water names"""
return self._get_text_for_category(SEARCH_CATEGORY_BODY_OF_WATER)
return self._get_text_for_category(self._categories.BODY_OF_WATER)
@property
def holidays(self):
"""returns list of holiday names"""
return self._get_text_for_category(SEARCH_CATEGORY_HOLIDAY)
return self._get_text_for_category(self._categories.HOLIDAY)
@property
def activities(self):
"""returns list of activity names"""
return self._get_text_for_category(SEARCH_CATEGORY_ACTIVITY)
return self._get_text_for_category(self._categories.ACTIVITY)
@property
def season(self):
"""returns season name"""
season = self._get_text_for_category(SEARCH_CATEGORY_SEASON)
season = self._get_text_for_category(self._categories.SEASON)
return season[0] if season else ""
@property
def venues(self):
"""returns list of venue names"""
return self._get_text_for_category(SEARCH_CATEGORY_VENUE)
return self._get_text_for_category(self._categories.VENUE)
@property
def venue_types(self):
"""returns list of venue types"""
return self._get_text_for_category(SEARCH_CATEGORY_VENUE_TYPE)
return self._get_text_for_category(self._categories.VENUE_TYPE)
@property
def media_types(self):
"""returns list of media types (photo, video, panorama, etc)"""
types = []
for category in SEARCH_CATEGORY_MEDIA_TYPES:
for category in self._categories.MEDIA_TYPES:
types += self._get_text_for_category(category)
return types