Bug labels ventura 816 (#823)
* Partial fix for #816, labels on Ventura / macOS 13.0 * Version bump
This commit is contained in:
parent
6dbeaae541
commit
ae560d24cb
@ -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}
|
||||
|
||||
|
||||
@ -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|
|
||||
|
||||
@ -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|
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.52.0"
|
||||
__version__ = "0.53.0"
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)}."
|
||||
)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user