This commit is contained in:
Rhet Turnbull 2023-05-07 07:33:07 -07:00 committed by GitHub
parent ee8053d867
commit f0aa69d8bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 55 additions and 38 deletions

View File

@ -119,7 +119,9 @@ def rich_echo(
# if not outputting to terminal, use a huge width to avoid wrapping # if not outputting to terminal, use a huge width to avoid wrapping
# otherwise tests fail # otherwise tests fail
width = 10_000 width = 10_000
console = get_rich_console() or Console(theme=theme or get_rich_theme(), width=width) console = get_rich_console() or Console(
theme=theme or get_rich_theme(), width=width
)
if markdown: if markdown:
message = Markdown(message) message = Markdown(message)
# Markdown always adds a new line so disable unless explicitly specified # Markdown always adds a new line so disable unless explicitly specified

View File

@ -7,28 +7,25 @@ if is_macos:
import objc import objc
import Foundation import Foundation
def theme(): def theme():
with objc.autorelease_pool(): with objc.autorelease_pool():
user_defaults = Foundation.NSUserDefaults.standardUserDefaults() user_defaults = Foundation.NSUserDefaults.standardUserDefaults()
system_theme = user_defaults.stringForKey_("AppleInterfaceStyle") system_theme = user_defaults.stringForKey_("AppleInterfaceStyle")
return "dark" if system_theme == "Dark" else "light" return "dark" if system_theme == "Dark" else "light"
def is_dark_mode(): def is_dark_mode():
return theme() == "dark" return theme() == "dark"
def is_light_mode(): def is_light_mode():
return theme() == "light" return theme() == "light"
else: else:
def theme(): def theme():
return "light" return "light"
def is_dark_mode(): def is_dark_mode():
return theme() == "dark" return theme() == "dark"
def is_light_mode(): def is_light_mode():
return theme() == "light" return theme() == "light"

View File

@ -57,8 +57,8 @@ def dump(
print_template, print_template,
): ):
"""Print list of all photos & associated info from the Photos library. """Print list of all photos & associated info from the Photos library.
NOTE: dump is DEPRECATED and will be removed in a future release. NOTE: dump is DEPRECATED and will be removed in a future release.
Use `osxphotos query` instead. Use `osxphotos query` instead.
""" """

View File

@ -255,7 +255,9 @@ class ExportCommand(click.Command):
if is_macos: if is_macos:
formatter.write( formatter.write(
rich_text("## Extended Attributes", width=formatter.width, markdown=True) rich_text(
"## Extended Attributes", width=formatter.width, markdown=True
)
) )
formatter.write("\n") formatter.write("\n")
formatter.write_text( formatter.write_text(

View File

@ -68,7 +68,6 @@ class PathOrStdin(click.Path):
class DateTimeISO8601(click.ParamType): class DateTimeISO8601(click.ParamType):
name = "DATETIME" name = "DATETIME"
def convert(self, value, param, ctx): def convert(self, value, param, ctx):
@ -82,7 +81,6 @@ class DateTimeISO8601(click.ParamType):
class BitMathSize(click.ParamType): class BitMathSize(click.ParamType):
name = "BITMATH" name = "BITMATH"
def convert(self, value, param, ctx): def convert(self, value, param, ctx):
@ -102,7 +100,6 @@ class BitMathSize(click.ParamType):
class TimeISO8601(click.ParamType): class TimeISO8601(click.ParamType):
name = "TIME" name = "TIME"
def convert(self, value, param, ctx): def convert(self, value, param, ctx):
@ -141,7 +138,6 @@ class FunctionCall(click.ParamType):
class ExportDBType(click.ParamType): class ExportDBType(click.ParamType):
name = "EXPORTDB" name = "EXPORTDB"
def convert(self, value, param, ctx): def convert(self, value, param, ctx):
@ -279,7 +275,6 @@ class StrpDateTimePattern(click.ParamType):
class Latitude(click.ParamType): class Latitude(click.ParamType):
name = "Latitude" name = "Latitude"
def convert(self, value, param, ctx): def convert(self, value, param, ctx):
@ -295,7 +290,6 @@ class Latitude(click.ParamType):
class Longitude(click.ParamType): class Longitude(click.ParamType):
name = "Longitude" name = "Longitude"
def convert(self, value, param, ctx): def convert(self, value, param, ctx):

View File

@ -32,16 +32,21 @@ from .list import _list_libraries
from .print_photo_info import print_photo_fields, print_photo_info from .print_photo_info import print_photo_fields, print_photo_info
from .verbose import get_verbose_console from .verbose import get_verbose_console
MACOS_OPTIONS = make_click_option_decorator(*[ MACOS_OPTIONS = make_click_option_decorator(
click.Option( *[
["--add-to-album"], click.Option(
metavar="ALBUM", ["--add-to-album"],
help="Add all photos from query to album ALBUM in Photos. Album ALBUM will be created " metavar="ALBUM",
"if it doesn't exist. All photos in the query results will be added to this album. " help="Add all photos from query to album ALBUM in Photos. Album ALBUM will be created "
"This only works if the Photos library being queried is the last-opened (default) library in Photos. " "if it doesn't exist. All photos in the query results will be added to this album. "
"This feature is currently experimental. I don't know how well it will work on large query sets.", "This only works if the Photos library being queried is the last-opened (default) library in Photos. "
), "This feature is currently experimental. I don't know how well it will work on large query sets.",
] if is_macos else []) ),
]
if is_macos
else []
)
@click.command() @click.command()
@DB_OPTION @DB_OPTION

View File

@ -58,6 +58,7 @@ def repl(ctx, cli_obj, db, emacs, beta, **kwargs):
import logging import logging
from objexplore import explore from objexplore import explore
if is_macos: if is_macos:
from photoscript import Album, Photo, PhotosLibrary from photoscript import Album, Photo, PhotosLibrary
from rich import inspect as _inspect from rich import inspect as _inspect

View File

@ -182,7 +182,9 @@ class ExportReportWriterSQLite(ReportWriterABC):
with suppress(FileNotFoundError): with suppress(FileNotFoundError):
os.unlink(self.output_file) os.unlink(self.output_file)
self._conn = sqlite3.connect(self.output_file, check_same_thread=SQLITE_CHECK_SAME_THREAD) self._conn = sqlite3.connect(
self.output_file, check_same_thread=SQLITE_CHECK_SAME_THREAD
)
self._create_tables() self._create_tables()
self.report_id = self._generate_report_id() self.report_id = self._generate_report_id()
@ -534,7 +536,9 @@ class SyncReportWriterSQLite(ReportWriterABC):
with suppress(FileNotFoundError): with suppress(FileNotFoundError):
os.unlink(self.output_file) os.unlink(self.output_file)
self._conn = sqlite3.connect(self.output_file, check_same_thread=SQLITE_CHECK_SAME_THREAD) self._conn = sqlite3.connect(
self.output_file, check_same_thread=SQLITE_CHECK_SAME_THREAD
)
self._create_tables() self._create_tables()
self.report_id = self._generate_report_id() self.report_id = self._generate_report_id()

View File

@ -170,6 +170,7 @@ def get_photo_metadata(photos: list[PhotoInfo]) -> str:
photos_dict[k] = v photos_dict[k] = v
elif photos_dict[k] and v != photos_dict[k]: elif photos_dict[k] and v != photos_dict[k]:
photos_dict[k] = f"{photos_dict[k]} {v}" photos_dict[k] = f"{photos_dict[k]} {v}"
# convert photos_dict to JSON string # convert photos_dict to JSON string
# wouldn't it be nice if json encoder handled datetimes... # wouldn't it be nice if json encoder handled datetimes...
def default(o): def default(o):

View File

@ -21,6 +21,7 @@ __all__ = [
"utc_offset_seconds", "utc_offset_seconds",
] ]
# TODO: look at https://github.com/regebro/tzlocal for more robust implementation # TODO: look at https://github.com/regebro/tzlocal for more robust implementation
def get_local_tz(dt: datetime.datetime) -> datetime.tzinfo: def get_local_tz(dt: datetime.datetime) -> datetime.tzinfo:
"""Return local timezone as datetime.timezone tzinfo for dt """Return local timezone as datetime.timezone tzinfo for dt
@ -207,4 +208,3 @@ def utc_offset_seconds(dt: datetime.datetime) -> int:
return dt.tzinfo.utcoffset(dt).total_seconds() return dt.tzinfo.utcoffset(dt).total_seconds()
else: else:
raise ValueError("dt does not have timezone info") raise ValueError("dt does not have timezone info")

View File

@ -23,7 +23,7 @@ import sys
import threading import threading
import time import time
assert(sys.platform == "darwin") assert sys.platform == "darwin"
import AVFoundation import AVFoundation
import CoreServices import CoreServices
@ -87,6 +87,7 @@ PHOTOKIT_NOTIFICATION_FINISHED_REQUEST = "PyPhotoKitNotificationFinishedRequest"
# minimum amount to sleep while waiting for export # minimum amount to sleep while waiting for export
MIN_SLEEP = 0.015 MIN_SLEEP = 0.015
### utility functions ### utility functions
def NSURL_to_path(url): def NSURL_to_path(url):
"""Convert URL string as represented by NSURL to a path string""" """Convert URL string as represented by NSURL to a path string"""
@ -629,7 +630,8 @@ class PhotoAsset:
def handler(imageData, dataUTI, orientation, info): def handler(imageData, dataUTI, orientation, info):
"""result handler for requestImageDataAndOrientationForAsset_options_resultHandler_ """result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
all returned by the request is set as properties of nonlocal data (Fetchdata object)""" all returned by the request is set as properties of nonlocal data (Fetchdata object)
"""
nonlocal requestdata nonlocal requestdata
@ -676,7 +678,8 @@ class PhotoAsset:
def handler(data): def handler(data):
"""result handler for requestImageDataAndOrientationForAsset_options_resultHandler_ """result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
all returned by the request is set as properties of nonlocal data (Fetchdata object)""" all returned by the request is set as properties of nonlocal data (Fetchdata object)
"""
nonlocal requestdata nonlocal requestdata
@ -713,7 +716,8 @@ class PhotoAsset:
def handler(imageData, dataUTI, orientation, info): def handler(imageData, dataUTI, orientation, info):
"""result handler for requestImageDataAndOrientationForAsset_options_resultHandler_ """result handler for requestImageDataAndOrientationForAsset_options_resultHandler_
all returned by the request is set as properties of nonlocal data (Fetchdata object)""" all returned by the request is set as properties of nonlocal data (Fetchdata object)
"""
nonlocal data nonlocal data

View File

@ -309,7 +309,9 @@ def _process_faceinfo_5(photosdb):
face["righteyey"] = row[34] face["righteyey"] = row[34]
face["roll"] = row[35] face["roll"] = row[35]
face["size"] = row[36] face["size"] = row[36]
face["yaw"] = 0 # Photos 4 only (this is in Photos 5-7, but dropped in Ventura so just don't support it) face[
"yaw"
] = 0 # Photos 4 only (this is in Photos 5-7, but dropped in Ventura so just don't support it)
face["pitch"] = 0 # not defined in Photos 5 face["pitch"] = 0 # not defined in Photos 5
photosdb._db_faceinfo_pk[pk] = face photosdb._db_faceinfo_pk[pk] = face

View File

@ -71,6 +71,7 @@ PlaceNames = namedtuple(
], ],
) )
# The following classes represent Photo Library Reverse Geolocation Info as stored # The following classes represent Photo Library Reverse Geolocation Info as stored
# in ZADDITIONALASSETATTRIBUTES.ZREVERSELOCATIONDATA # in ZADDITIONALASSETATTRIBUTES.ZREVERSELOCATIONDATA
# These classes are used by bpylist.archiver to unarchive the serialized objects # These classes are used by bpylist.archiver to unarchive the serialized objects

View File

@ -29,7 +29,7 @@ def sqlgrep(
flags = re.IGNORECASE if ignore_case else 0 flags = re.IGNORECASE if ignore_case else 0
try: try:
with sqlite3.connect(f"file:{filename}?mode=ro", uri=True) as conn: with sqlite3.connect(f"file:{filename}?mode=ro", uri=True) as conn:
regex = re.compile(f'({pattern})', flags=flags) regex = re.compile(f"({pattern})", flags=flags)
filename_header = f"{filename}: " if print_filename else "" filename_header = f"{filename}: " if print_filename else ""
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
cursor = conn.cursor() cursor = conn.cursor()

View File

@ -17,12 +17,10 @@ if is_macos:
import Foundation import Foundation
import objc import objc
def known_timezone_names(): def known_timezone_names():
"""Get list of valid timezones on macOS""" """Get list of valid timezones on macOS"""
return Foundation.NSTimeZone.knownTimeZoneNames() return Foundation.NSTimeZone.knownTimeZoneNames()
class Timezone: class Timezone:
"""Create Timezone object from either name (str) or offset from GMT (int)""" """Create Timezone object from either name (str) or offset from GMT (int)"""
@ -63,6 +61,7 @@ if is_macos:
if isinstance(other, Timezone): if isinstance(other, Timezone):
return self.timezone == other.timezone return self.timezone == other.timezone
return False return False
else: else:
import zoneinfo import zoneinfo
from datetime import datetime from datetime import datetime
@ -71,7 +70,6 @@ else:
"""Get list of valid timezones""" """Get list of valid timezones"""
return zoneinfo.available_timezones() return zoneinfo.available_timezones()
class Timezone: class Timezone:
"""Create Timezone object from either name (str) or offset from GMT (int)""" """Create Timezone object from either name (str) or offset from GMT (int)"""

View File

@ -520,10 +520,13 @@ def _load_uti_dict():
EXT_UTI_DICT[row["extension"]] = row["UTI"] EXT_UTI_DICT[row["extension"]] = row["UTI"]
UTI_EXT_DICT[row["UTI"]] = row["preferred_extension"] UTI_EXT_DICT[row["UTI"]] = row["preferred_extension"]
_load_uti_dict() _load_uti_dict()
# OS version for determining which methods can be used # OS version for determining which methods can be used
OS_VER, OS_MAJOR, _ = (int(x) for x in get_macos_version()) if is_macos else (None, None, None) OS_VER, OS_MAJOR, _ = (
(int(x) for x in get_macos_version()) if is_macos else (None, None, None)
)
def _get_uti_from_mdls(extension): def _get_uti_from_mdls(extension):

View File

@ -56,6 +56,7 @@ VERSION_INFO_URL = "https://pypi.org/pypi/osxphotos/json"
is_macos = sys.platform == "darwin" is_macos = sys.platform == "darwin"
def assert_macos(): def assert_macos():
assert is_macos, "This feature only runs on macOS" assert is_macos, "This feature only runs on macOS"
@ -280,6 +281,8 @@ def list_photo_libraries():
T = TypeVar("T", bound=Union[str, pathlib.Path]) T = TypeVar("T", bound=Union[str, pathlib.Path])
def normalize_fs_path(path: T) -> T: def normalize_fs_path(path: T) -> T:
"""Normalize filesystem paths with unicode in them""" """Normalize filesystem paths with unicode in them"""
form = "NFD" if is_macos else "NFC" form = "NFD" if is_macos else "NFC"