Updated search info for Ventura, #937 (#943)

This commit is contained in:
Rhet Turnbull
2023-01-21 10:29:31 -08:00
committed by GitHub
parent 8c46b8e545
commit 69eb4b070c
13 changed files with 265 additions and 100 deletions

View File

@@ -1,6 +1,6 @@
"""
Constants used by osxphotos
"""
""" Constants used by osxphotos """
from __future__ import annotations
import os.path
from datetime import datetime
@@ -138,7 +138,9 @@ _PHOTOS_5_SHARED_PHOTO_PATH = "resources/cloudsharing/data"
_PHOTOS_8_SHARED_PHOTO_PATH = "scopes/cloudsharing/data"
# Where are shared iCloud derivatives located?
_PHOTOS_5_SHARED_DERIVATIVE_PATH = "resources/cloudsharing/resources/derivatives/masters"
_PHOTOS_5_SHARED_DERIVATIVE_PATH = (
"resources/cloudsharing/resources/derivatives/masters"
)
_PHOTOS_8_SHARED_DERIVATIVE_PATH = "scopes/cloudsharing/resources/derivatives/masters"
# What type of file? Based on ZGENERICASSET.ZKIND in Photos 5 database
@@ -213,11 +215,11 @@ class SearchCategory:
TITLE = 2017
DESCRIPTION = 2018
HOME = 2020
WORK = 2036
PERSON = 2021
ACTIVITY = 2027
HOLIDAY = 2029
SEASON = 2030
WORK = 2036
VENUE = 2038
VENUE_TYPE = 2039
PHOTO_TYPE_VIDEO = 2044
@@ -230,6 +232,7 @@ class SearchCategory:
PHOTO_TYPE_PORTRAIT = 2053
PHOTO_TYPE_SELFIES = 2054
PHOTO_TYPE_FAVORITES = 2055
PHOTO_TYPE_ANIMATED = None # Photos 8+ only
MEDIA_TYPES = [
PHOTO_TYPE_VIDEO,
PHOTO_TYPE_SLOMO,
@@ -244,7 +247,23 @@ class SearchCategory:
]
PHOTO_NAME = 2056
CAMERA = None # Photos 8+ only
TEXT_FOUND = None # Photos 8+ only
DETECTED_TEXT = None # Photos 8+ only
SOURCE = None # Photos 8+ only
@classmethod
def categories(cls) -> dict[int, str]:
"""Return categories as dict of value: name"""
# a bit of a hack to basically reverse the enum
return {
value: name
for name, value in cls.__dict__.items()
if name is not None
and not name.startswith("__")
and not callable(name)
and name.isupper()
and not isinstance(value, (list, dict, tuple))
}
class SearchCategory_Photos8(SearchCategory):
@@ -252,6 +271,20 @@ class SearchCategory_Photos8(SearchCategory):
# Many of the category values changed in Ventura / Photos 8
# and some new categories were added
CITY = 5
LOCALITY_4 = 4
SUB_LOCALITY_5 = None
SUB_LOCALITY_6 = 6
LOCALITY_8 = 8
NAMED_AREA = 7
ALL_LOCALITY = [
LOCALITY_4,
SUB_LOCALITY_6,
LOCALITY_8,
NAMED_AREA,
]
HOME = 1000
WORK = 1001
LABEL = 1500
MONTH = 1100
YEAR = 1101
@@ -261,11 +294,55 @@ class SearchCategory_Photos8(SearchCategory):
TITLE = 1201
DESCRIPTION = 1202
DETECTED_TEXT = 1203 # new in Photos 8
TEXT_FOUND = 1205 # new in Photos 8
PERSON = 1300
ACTIVITY = 1600
VENUE = 1700
VENUE_TYPE = 1701
PHOTO_TYPE_VIDEO = 1901
PHOTO_TYPE_SELFIES = 1915
PHOTO_TYPE_LIVE = 1906
PHOTO_TYPE_PORTRAIT = 1914
PHOTO_TYPE_FAVORITES = 2000
PHOTO_TYPE_PANORAMA = 1908
PHOTO_TYPE_TIMELAPSE = 1909
PHOTO_TYPE_SLOMO = 1905
PHOTO_TYPE_BURSTS = 1913
PHOTO_TYPE_SCREENSHOT = 1907
PHOTO_TYPE_ANIMATED = 1912
PHOTO_TYPE_RAW = 1902
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_TYPE_ANIMATED,
]
PHOTO_NAME = 2100
CAMERA = 2300 # new in Photos 8
SOURCE = 2200 # new in Photos 8, shows the app/software source for the photo, e.g. Messages, Safari, etc.
@classmethod
def categories(cls) -> dict[int, str]:
"""Return categories as dict of value: name"""
# need to get the categories from the base class and update with the new values
classdict = SearchCategory.__dict__.copy()
classdict |= cls.__dict__.copy()
return {
value: name
for name, value in classdict.items()
if name is not None
and not name.startswith("__")
and not callable(name)
and name.isupper()
and not isinstance(value, (list, dict, tuple))
}
def search_category_factory(version: int) -> SearchCategory:

View File

@@ -21,7 +21,7 @@ from rich.live import Live
from rich.panel import Panel
from osxphotos import PhotoInfo, PhotosDB
from osxphotos._constants import _UNKNOWN_PERSON
from osxphotos._constants import _UNKNOWN_PERSON, search_category_factory
from osxphotos.rich_utils import add_rich_markup_tag
from osxphotos.text_detection import detect_text as detect_text_in_photo
from osxphotos.utils import dd_to_dms_str
@@ -65,6 +65,22 @@ def trim(text: str, pad: str = "") -> str:
return text if len(text) <= width else f"{text[: width- 3]}..."
def format_search_info(photo: PhotoInfo) -> str:
"""Format search info for photo"""
categories = sorted(list(photo._db._db_searchinfo_categories.keys()))
search_info = photo.search_info
if not search_info:
return ""
search_info_strs = []
category_dict = search_category_factory(photo._db.photos_version).categories()
for category in categories:
if text := search_info._get_text_for_category(category):
text = ", ".join(t for t in text if t) if isinstance(text, list) else text
category_name = str(category_dict.get(category, category)).lower()
search_info_strs.append(f"{bold(category_name)}: {text}")
return ", ".join(search_info_strs)
def inspect_photo(
photo: PhotoInfo,
detected_text: Optional[str] = None,
@@ -138,8 +154,10 @@ def inspect_photo(
+ f"{', '.join(dd_to_dms_str(*photo.location)) if photo.location[0] else '-'}",
bold("Place: ") + f"{photo.place.name if photo.place else '-'}",
bold("Categories/Labels: ") + f"{', '.join(photo.labels) or '-'}",
bold("Search Info: ") + format_search_info(photo),
]
)
properties.append(format_flags(photo))
properties.append(format_albums(photo))

View File

@@ -324,8 +324,15 @@ class PhotosDB:
# _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
db_ver_int = int(self._db_version)
if db_ver_int < 3000:
self._photos_ver = 2
elif db_ver_int < 4000:
self._photos_ver = 3
elif db_ver_int < 5000:
self._photos_ver = 4
else:
self._photos_ver = 5
# If Photos >= 5, actual data isn't in photos.db but in Photos.sqlite
if int(self._db_version) > int(_PHOTOS_4_VERSION):
dbpath = pathlib.Path(self._dbfile).parent
@@ -585,6 +592,11 @@ class PhotosDB:
"""returns path to the Photos library PhotosDB was initialized with"""
return self._library_path
@property
def photos_version(self):
"""returns version of Photos app that created the library"""
return self._photos_ver
def get_db_connection(self):
"""Get connection to the working copy of the Photos database

View File

@@ -139,6 +139,13 @@ class SearchInfo:
return []
return self._get_text_for_category(self._categories.DETECTED_TEXT)
@property
def text_found(self):
"""Returns True if photos has detected text (macOS 13+ / Photos 8+ only)"""
if self._photo._db._photos_ver < 8:
return []
return self._get_text_for_category(self._categories.TEXT_FOUND)
@property
def camera(self):
"""returns camera name (macOS 13+ / Photos 8+ only)"""
@@ -147,10 +154,18 @@ class SearchInfo:
camera = self._get_text_for_category(self._categories.CAMERA)
return camera[0] if camera else ""
@property
def source(self):
"""returns source of the photo (e.g. "Messages", "Safar", etc) (macOS 13+ / Photos 8+ only)"""
if self._photo._db._photos_ver < 8:
return ""
source = self._get_text_for_category(self._categories.SOURCE)
return source[0] if source else ""
@property
def all(self):
"""return all search info properties in a single list"""
all = (
all_ = (
self.labels
+ self.place_names
+ self.streets
@@ -165,23 +180,23 @@ class SearchInfo:
+ self.detected_text
)
if self.city:
all += [self.city]
all_ += [self.city]
if self.state:
all += [self.state]
all_ += [self.state]
if self.state_abbreviation:
all += [self.state_abbreviation]
all_ += [self.state_abbreviation]
if self.country:
all += [self.country]
all_ += [self.country]
if self.month:
all += [self.month]
all_ += [self.month]
if self.year:
all += [self.year]
all_ += [self.year]
if self.season:
all += [self.season]
all_ += [self.season]
if self.camera:
all += [self.camera]
all_ += [self.camera]
return all
return all_
def asdict(self):
"""return dict of search info"""
@@ -206,6 +221,7 @@ class SearchInfo:
"media_types": self.media_types,
"detected_text": self.detected_text,
"camera": self.camera,
"source": self.source,
}
def _get_text_for_category(self, category):