Added PhotoInfo.place for reverse geolocation data

This commit is contained in:
Rhet Turnbull 2020-03-21 10:39:42 -07:00
parent 816b98e617
commit b338b34d50
149 changed files with 1502 additions and 31 deletions

View File

@ -13,6 +13,7 @@
* [Package Interface](#package-interface)
+ [PhotosDB](#photosdb)
+ [PhotoInfo](#photoinfo)
+ [PlaceInfo](#placeinfo)
+ [Utility Functions](#utility-functions)
+ [Examples](#examples)
* [Related Projects](#related-projects)
@ -632,6 +633,9 @@ Returns `True` if the picture has been marked as hidden, otherwise `False`
#### `location`
Returns latitude and longitude as a tuple of floats (latitude, longitude). If location is not set, latitude and longitude are returned as `None`
#### `place`
Returns a [PlaceInfo](#PlaceInfo) object with reverse geolocation data or None if there is the photo has no reverse geolocation information.
#### `shared`
Returns True if photo is in a shared album, otherwise False.
@ -744,9 +748,63 @@ Then
If overwrite=False and increment=False, export will fail if destination file already exists
**Implementation Note**: Because the usual python file copy methods don't preserve all the metadata available on MacOS, export uses /usr/bin/ditto to do the copy for export. ditto preserves most metadata such as extended attributes, permissions, ACLs, etc.
### PlaceInfo
[PhotoInfo.place](#place) returns a PlaceInfo object if the photo contains valid reverse geolocation information. PlaceInfo has the following properties.
**Note** For Photos versions <= 4, only `name`, `names`, and `country_code` properties are defined. All others return `None`. This is because older versions of Photos do not store the more detailed reverse geolocation information.
#### `ishome`
Returns `True` if photo place is user's home address, otherwise `False`.
#### `name`
Returns the name of the local place as str. This may be a street address (e.g. "2038 18th St NW") or a public place (e.g. "St James\'s Park").
Returns `None` if photo does not contain a name.
#### `names`
Returns a list of place names in ascending order by area, starting with the smallest area (most local) to largest area (least local). For example:
["2038 18th St NW",
"Adams Morgan",
"Washington",
"Washington",
"Washington",
"District of Columbia",
"United States"]
`names[0]` will always be the most local (e.g. street address) component and `names[-1]` will always be the least local which is almost always the country name.
**Note**: names may contain duplicates as in above. The data is returned exactly as it is stored by Photos.
#### `country_code`
Returns the country_code of place, for example "GB". Returns `None` if PhotoInfo contains no country code.
#### `address_str`
Returns the full postal address as a string if defined, otherwise `None`.
For example: "2038 18th St NW, Washington, DC 20009, United States"
#### `address`:
Returns a `PostalAddress` tuple with details of the postal address containing the following fields:
- city
- country
- postal_code
- state
- street
- sub_administrative_area
- sub_locality
- iso_country_code
For example:
```python
>>> photo.place.address
PostalAddress(street='3700 Wailea Alanui Dr', sub_locality=None, city='Kihei', sub_administrative_area='Maui', state='HI', postal_code='96753', country='United States', iso_country_code='US')
>>> photo.place.address.postal_code
'96753'
```
### Utility Functions
The following functions are located in osxphotos.utils

View File

@ -1,3 +1,3 @@
""" version info """
__version__ = "0.22.23"
__version__ = "0.23.0"

View File

@ -26,13 +26,14 @@ from ._constants import (
_TEMPLATE_DIR,
_XMP_TEMPLATE_NAME,
)
from .exiftool import ExifTool
from .placeinfo import PlaceInfo4, PlaceInfo5
from .utils import (
_copy_file,
_export_photo_uuid_applescript,
_get_resource_loc,
dd_to_dms_str,
)
from .exiftool import ExifTool
class PhotoInfo:
@ -489,6 +490,34 @@ class PhotoInfo:
""" Returns True if photo is a selfie (front facing camera), otherwise False """
return self._info["selfie"]
@property
def place(self):
""" If Photos version >= 5, returns PlaceInfo object containing reverse geolocation info """
# implementation note: doesn't create the PlaceInfo object until requested
# then memoizes the object in self._place to avoid recreating the object
if self._db._db_version < _PHOTOS_5_VERSION:
try:
return self._place # pylint: disable=access-member-before-definition
except:
if self._info["placeNames"]:
self._place = PlaceInfo4(
self._info["placeNames"], self._info["countryCode"]
)
else:
self._place = None
return self._place
else:
try:
return self._place # pylint: disable=access-member-before-definition
except AttributeError:
if self._info["reverse_geolocation"]:
self._place = PlaceInfo5(self._info["reverse_geolocation"])
else:
self._place = None
return self._place
def export(
self,
dest,

View File

@ -28,11 +28,11 @@ from ._version import __version__
from .photoinfo import PhotoInfo
from .utils import (
_check_file_exists,
_get_os_version,
get_last_library_path,
_debug,
_open_sql_file,
_db_is_locked,
_debug,
_get_os_version,
_open_sql_file,
get_last_library_path,
)
# TODO: Add test for imageTimeZoneOffsetSeconds = None
@ -830,6 +830,52 @@ class PhotosDB:
self._dbphotos[uuid]["cloudStatus"] = row[3]
self._dbphotos[uuid]["incloud"] = True if row[2] == 1 else False
# get location data
# get the country codes
country_codes = c.execute(
"SELECT modelID, countryCode "
"FROM RKPlace "
"WHERE countryCode IS NOT NULL "
).fetchall()
countries = {code[0]: code[1] for code in country_codes}
self._db_countries = countries
# save existing row_factory
old_row_factory = c.row_factory
# want only the list of values, not a list of tuples
c.row_factory = lambda cursor, row: row[0]
for uuid in self._dbphotos:
# get placeId which is then used to lookup defaultName
place_ids = c.execute(
"SELECT placeId "
"FROM RKPlaceForVersion "
f"WHERE versionId = '{self._dbphotos[uuid]['modelID']}'"
).fetchall()
self._dbphotos[uuid]["placeIDs"] = place_ids
country_code = [countries[x] for x in place_ids if x in countries]
if len(country_code) > 1:
logging.warning(f"Found more than one country code for uuid: {uuid}")
if country_code:
self._dbphotos[uuid]["countryCode"] = country_code[0]
else:
self._dbphotos[uuid]["countryCode"] = None
place_names = c.execute(
"SELECT DISTINCT defaultName AS name "
"FROM RKPlace "
f"WHERE modelId IN({','.join(map(str,place_ids))}) "
"ORDER BY area ASC "
).fetchall()
self._dbphotos[uuid]["placeNames"] = place_names
self._dbphotos[uuid]["reverse_geolocation"] = None # Photos 5
# restore row_factory
c.row_factory = old_row_factory
# build album_titles dictionary
for album_id in self._dbalbum_details:
title = self._dbalbum_details[album_id]["title"]
@ -1045,7 +1091,8 @@ class PhotosDB:
ZGENERICASSET.ZKINDSUBTYPE,
ZGENERICASSET.ZCUSTOMRENDEREDVALUE,
ZADDITIONALASSETATTRIBUTES.ZCAMERACAPTUREDEVICE,
ZGENERICASSET.ZCLOUDASSETGUID
ZGENERICASSET.ZCLOUDASSETGUID,
ZADDITIONALASSETATTRIBUTES.ZREVERSELOCATIONDATA
FROM ZGENERICASSET
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = ZGENERICASSET.Z_PK
WHERE ZGENERICASSET.ZTRASHEDSTATE = 0
@ -1076,8 +1123,9 @@ class PhotosDB:
# 21 ZGENERICASSET.ZKINDSUBTYPE -- determine if live photos, etc
# 22 ZGENERICASSET.ZCUSTOMRENDEREDVALUE -- determine if HDR photo
# 23 ZADDITIONALASSETATTRIBUTES.ZCAMERACAPTUREDEVICE -- 1 if selfie (front facing camera)
# 25 ZGENERICASSET.ZCLOUDASSETGUID -- not null if asset is cloud asset
# 24 ZGENERICASSET.ZCLOUDASSETGUID -- not null if asset is cloud asset
# (e.g. user has "iCloud Photos" checked in Photos preferences)
# 25 ZADDITIONALASSETATTRIBUTES.ZREVERSELOCATIONDATA -- reverse geolocation data
for row in c:
uuid = row[0]
@ -1203,6 +1251,12 @@ class PhotosDB:
info["cloudStatus"] = None # Photos 4
info["cloudAvailable"] = None # Photos 4
# reverse geolocation info
info["reverse_geolocation"] = row[25]
info["placeIDs"] = None # Photos 4
info["placeNames"] = None # Photos 4
info["countryCode"] = None # Photos 4
self._dbphotos[uuid] = info
# # if row[19] is not None and ((row[20] == 2) or (row[20] == 4)):
@ -1371,6 +1425,9 @@ class PhotosDB:
else:
self._dbalbum_titles[title] = [album_id]
# country codes (only used in Photos <=4)
self._db_countries = None
# close connection and remove temporary files
conn.close()
@ -1715,8 +1772,8 @@ class PhotosDB:
info.burst_key = True # it's a key photo (selected from the burst)
else:
info.burst_key = (
False
) # it's a burst photo but not one that's selected
False # it's a burst photo but not one that's selected
)
else:
# not a burst photo
info.burst = False

433
osxphotos/placeinfo.py Normal file
View File

@ -0,0 +1,433 @@
"""
PlaceInfo class
Provides reverse geolocation info for photos
"""
from abc import ABC, abstractmethod
from collections import namedtuple
from bpylist import archiver
# postal address information, returned by PlaceInfo.address
PostalAddress = namedtuple(
"PostalAddress",
[
"street",
"sub_locality",
"city",
"sub_administrative_area",
"state",
"postal_code",
"country",
"iso_country_code",
],
)
# The following classes represent Photo Library Reverse Geolocation Info as stored
# in ZADDITIONALASSETATTRIBUTES.ZREVERSELOCATIONDATA
# These classes are used by bpylist.archiver to unarchive the serialized objects
class PLRevGeoLocationInfo:
""" The top level reverse geolocation object """
def __init__(
self,
addressString,
countryCode,
mapItem,
isHome,
compoundNames,
compoundSecondaryNames,
version,
geoServiceProvider,
postalAddress,
):
self.addressString = addressString
self.countryCode = countryCode
self.mapItem = mapItem
self.isHome = isHome
self.compoundNames = compoundNames
self.compoundSecondaryNames = compoundSecondaryNames
self.version = version
self.geoServiceProvider = geoServiceProvider
self.postalAddress = postalAddress
def __eq__(self, other):
for field in [
"addressString",
"countryCode",
"isHome",
"compoundNames",
"compoundSecondaryNames",
"version",
"geoServiceProvider",
"postalAddress",
]:
if getattr(self, field) != getattr(other, field):
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return f"addressString: {self.addressString}, countryCode: {self.countryCode}, isHome: {self.isHome}, mapItem: {self.mapItem}, postalAddress: {self.postalAddress}"
@staticmethod
def encode_archive(obj, archive):
archive.encode("addressString", obj.addressString)
archive.encode("countryCode", obj.countryCode)
archive.encode("mapItem", obj.mapItem)
archive.encode("isHome", obj.isHome)
archive.encode("compoundNames", obj.compoundNames)
archive.encode("compoundSecondaryNames", obj.compoundSecondaryNames)
archive.encode("version", obj.version)
archive.encode("geoServiceProvider", obj.geoServiceProvider)
archive.encode("postalAddress", obj.postalAddress)
@staticmethod
def decode_archive(archive):
addressString = archive.decode("addressString")
countryCode = archive.decode("countryCode")
mapItem = archive.decode("mapItem")
isHome = archive.decode("isHome")
compoundNames = archive.decode("compoundNames")
compoundSecondaryNames = archive.decode("compoundSecondaryNames")
version = archive.decode("version")
geoServiceProvider = archive.decode("geoServiceProvider")
postalAddress = archive.decode("postalAddress")
return PLRevGeoLocationInfo(
addressString,
countryCode,
mapItem,
isHome,
compoundNames,
compoundSecondaryNames,
version,
geoServiceProvider,
postalAddress,
)
class PLRevGeoMapItem:
""" Stores the list of place names, organized by area """
def __init__(self, sortedPlaceInfos, finalPlaceInfos):
self.sortedPlaceInfos = sortedPlaceInfos
self.finalPlaceInfos = finalPlaceInfos
def __eq__(self, other):
for field in ["sortedPlaceInfos", "finalPlaceInfos"]:
if getattr(self, field) != getattr(other, field):
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
sortedPlaceInfos = []
finalPlaceInfos = []
for place in self.sortedPlaceInfos:
sortedPlaceInfos.append(str(place))
for place in self.finalPlaceInfos:
finalPlaceInfos.append(str(place))
return (
f"finalPlaceInfos: {finalPlaceInfos}, sortedPlaceInfos: {sortedPlaceInfos}"
)
@staticmethod
def encode_archive(obj, archive):
archive.encode("sortedPlaceInfos", obj.sortedPlaceInfos)
archive.encode("finalPlaceInfos", obj.finalPlaceInfos)
@staticmethod
def decode_archive(archive):
sortedPlaceInfos = archive.decode("sortedPlaceInfos")
finalPlaceInfos = archive.decode("finalPlaceInfos")
return PLRevGeoMapItem(sortedPlaceInfos, finalPlaceInfos)
class PLRevGeoMapItemAdditionalPlaceInfo:
""" Additional info about individual places """
def __init__(self, area, name, placeType, dominantOrderType):
self.area = area
self.name = name
self.placeType = placeType
self.dominantOrderType = dominantOrderType
def __eq__(self, other):
for field in ["area", "name", "placeType", "dominantOrderType"]:
if getattr(self, field) != getattr(other, field):
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return f"area: {self.area}, name: {self.name}, placeType: {self.placeType}"
@staticmethod
def encode_archive(obj, archive):
archive.encode("area", obj.area)
archive.encode("name", obj.name)
archive.encode("placeType", obj.placeType)
archive.encode("dominantOrderType", obj.dominantOrderType)
@staticmethod
def decode_archive(archive):
area = archive.decode("area")
name = archive.decode("name")
placeType = archive.decode("placeType")
dominantOrderType = archive.decode("dominantOrderType")
return PLRevGeoMapItemAdditionalPlaceInfo(
area, name, placeType, dominantOrderType
)
class CNPostalAddress:
""" postal address for the reverse geolocation info """
def __init__(
self,
_ISOCountryCode,
_city,
_country,
_postalCode,
_state,
_street,
_subAdministrativeArea,
_subLocality,
):
self._ISOCountryCode = _ISOCountryCode
self._city = _city
self._country = _country
self._postalCode = _postalCode
self._state = _state
self._street = _street
self._subAdministrativeArea = _subAdministrativeArea
self._subLocality = _subLocality
def __eq__(self, other):
for field in [
"_ISOCountryCode",
"_city",
"_country",
"_postalCode",
"_state",
"_street",
"_subAdministrativeArea",
"_subLocality",
]:
if getattr(self, field) != getattr(other, field):
return False
return True
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return ", ".join(
map(
str,
[
self._street,
self._city,
self._subLocality,
self._subAdministrativeArea,
self._state,
self._postalCode,
self._country,
self._ISOCountryCode,
],
)
)
@staticmethod
def encode_archive(obj, archive):
archive.encode("_ISOCountryCode", obj._ISOCountryCode)
archive.encode("_country", obj._country)
archive.encode("_city", obj._city)
archive.encode("_postalCode", obj._postalCode)
archive.encode("_state", obj._state)
archive.encode("_street", obj._street)
archive.encode("_subAdministrativeArea", obj._subAdministrativeArea)
archive.encode("_subLocality", obj._subLocality)
@staticmethod
def decode_archive(archive):
_ISOCountryCode = archive.decode("_ISOCountryCode")
_country = archive.decode("_country")
_city = archive.decode("_city")
_postalCode = archive.decode("_postalCode")
_state = archive.decode("_state")
_street = archive.decode("_street")
_subAdministrativeArea = archive.decode("_subAdministrativeArea")
_subLocality = archive.decode("_subLocality")
return CNPostalAddress(
_ISOCountryCode,
_city,
_country,
_postalCode,
_state,
_street,
_subAdministrativeArea,
_subLocality,
)
# register the classes with bpylist.archiver
archiver.update_class_map({"CNPostalAddress": CNPostalAddress})
archiver.update_class_map(
{"PLRevGeoMapItemAdditionalPlaceInfo": PLRevGeoMapItemAdditionalPlaceInfo}
)
archiver.update_class_map({"PLRevGeoMapItem": PLRevGeoMapItem})
archiver.update_class_map({"PLRevGeoLocationInfo": PLRevGeoLocationInfo})
class PlaceInfo(ABC):
@property
@abstractmethod
def address_str(self):
pass
@property
@abstractmethod
def country_code(self):
pass
@property
@abstractmethod
def ishome(self):
pass
@property
@abstractmethod
def name(self):
pass
@property
@abstractmethod
def names(self):
pass
@property
@abstractmethod
def address(self):
pass
class PlaceInfo4(PlaceInfo):
""" Reverse geolocation place info for a photo (Photos <= 4) """
def __init__(self, place_names, country_code):
""" place_names: list of place names in ascending order by area """
self._place_names = place_names
self._country_code = country_code
@property
def address_str(self):
return None
@property
def country_code(self):
return self._country_code
@property
def ishome(self):
return None
@property
def name(self):
return self._place_names[0]
@property
def names(self):
return self._place_names
@property
def address(self):
return PostalAddress(None, None, None, None, None, None, None, None)
def __eq__(self, other):
if not isinstance(other, type(self)):
return False
else:
return (
self._place_names == other._place_names
and self._country_code == other._country_code
)
def __ne__(self, other):
return not self.__eq__(other)
class PlaceInfo5(PlaceInfo):
""" Reverse geolocation place info for a photo (Photos >= 5) """
def __init__(self, revgeoloc_bplist):
""" revgeoloc_bplist: a binary plist blob containing
a serialized PLRevGeoLocationInfo object """
self._bplist = revgeoloc_bplist
# todo: check for None?
self._plrevgeoloc = archiver.unarchive(revgeoloc_bplist)
@property
def address_str(self):
""" returns the postal address as a string """
return self._plrevgeoloc.addressString
@property
def country_code(self):
""" returns the country code """
return self._plrevgeoloc.countryCode
@property
def ishome(self):
""" returns True if place is user's home address """
return self._plrevgeoloc.isHome
@property
def name(self):
""" returns local place name """
name = (
self._plrevgeoloc.mapItem.sortedPlaceInfos[0].name
if self._plrevgeoloc.mapItem.sortedPlaceInfos
else None
)
return name
@property
def names(self):
""" returns list of all place names in reverse order by area
e.g. most local is at index 0, least local (usually country) is at index -1 """
names = []
# todo: strip duplicates
for name in self._plrevgeoloc.mapItem.sortedPlaceInfos:
names.append(name.name)
return names
@property
def address(self):
addr = self._plrevgeoloc.postalAddress
address = PostalAddress(
street=addr._street,
sub_locality=addr._subLocality,
city=addr._city,
sub_administrative_area=addr._subAdministrativeArea,
state=addr._state,
postal_code=addr._postalCode,
country=addr._country,
iso_country_code=addr._ISOCountryCode,
)
return address
def __eq__(self, other):
if not isinstance(other, type(self)):
return False
else:
return self._plrevgeoloc == other._plrevgeoloc
def __ne__(self, other):
return not self.__eq__(other)

View File

@ -1,8 +1,12 @@
altgraph==0.17
ansimarkup==1.4.0
appdirs==1.4.3
astroid==2.2.5
atomicwrites==1.3.0
attrs==19.1.0
better-exceptions-fork==0.2.1.post6
black==19.10b0
bpylist2==2.0.3
certifi==2019.3.9
Click==7.0
colorama==0.4.1
@ -11,13 +15,20 @@ importlib-metadata==0.18
isort==4.3.20
lazy-object-proxy==1.4.1
loguru==0.2.5
macholib==1.14
Mako==1.1.1
MarkupSafe==1.1.1
mccabe==0.6.1
modulegraph==0.18
more-itertools==7.2.0
-e git+https://github.com/RhetTbull/osxphotos.git@0271b8ad9daf8b2fb80ce81e894478370e421379#egg=osxphotos
packaging==19.0
pathspec==0.7.0
pluggy==0.12.0
py==1.8.0
py2app==0.21
Pygments==2.4.2
PyInstaller==3.6
pyinstaller-setuptools==2019.3
pylint==2.3.1
pyobjc==6.0.1
pyobjc-core==6.0.1
@ -138,9 +149,11 @@ pytest==5.3.1
pytest-cov==2.8.1
pytest-sugar==0.9.2
PyYAML==5.1.2
regex==2020.2.20
six==1.12.0
termcolor==1.1.0
toml==0.10.0
typed-ast==1.4.1
wcwidth==0.1.7
wrapt==1.11.1
zipp==0.5.2
Mako==1.1.1

View File

@ -61,7 +61,13 @@ setup(
"Programming Language :: Python :: 3.6",
"Topic :: Software Development :: Libraries :: Python Modules",
],
install_requires=["pyobjc>=6.0.1", "Click>=7", "PyYAML>=5.1.2", "Mako>=1.1.1"],
install_requires=[
"pyobjc>=6.0.1",
"Click>=7",
"PyYAML>=5.1.2",
"Mako>=1.1.1",
"bpylist2>=2.0.3",
],
entry_points={"console_scripts": ["osxphotos=osxphotos.__main__:cli"]},
include_package_data=True,
)

View File

@ -3,8 +3,8 @@
<plist version="1.0">
<dict>
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
<date>2020-01-29T06:24:15Z</date>
<date>2020-03-19T20:25:48Z</date>
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
<date>2020-01-29T13:44:20Z</date>
<date>2020-03-19T22:36:41Z</date>
</dict>
</plist>

View File

@ -5,7 +5,7 @@
<key>LithiumMessageTracer</key>
<dict>
<key>LastReportedDate</key>
<date>2020-01-19T14:48:46Z</date>
<date>2020-03-15T20:19:24Z</date>
</dict>
</dict>
</plist>

View File

@ -11,6 +11,6 @@
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
<integer>1</integer>
<key>PLLastRevGeoVerFileFetchDateKey</key>
<date>2020-01-29T06:24:08Z</date>
<date>2020-03-15T20:18:33Z</date>
</dict>
</plist>

View File

@ -24,7 +24,7 @@
<key>SnapshotCompletedDate</key>
<date>2019-07-27T13:16:43Z</date>
<key>SnapshotLastValidated</key>
<date>2020-01-29T06:26:14Z</date>
<date>2020-03-19T20:27:27Z</date>
<key>SnapshotTables</key>
<dict/>
</dict>

View File

@ -3,24 +3,24 @@
<plist version="1.0">
<dict>
<key>BackgroundHighlightCollection</key>
<date>2020-03-15T10:17:08Z</date>
<date>2020-03-21T15:05:31Z</date>
<key>BackgroundHighlightEnrichment</key>
<date>2020-03-15T10:17:07Z</date>
<date>2020-03-21T15:05:31Z</date>
<key>BackgroundJobAssetRevGeocode</key>
<date>2020-03-15T11:59:15Z</date>
<date>2020-03-21T15:05:31Z</date>
<key>BackgroundJobSearch</key>
<date>2020-03-15T10:17:08Z</date>
<date>2020-03-21T15:05:31Z</date>
<key>BackgroundPeopleSuggestion</key>
<date>2020-03-15T10:17:07Z</date>
<date>2020-03-21T15:05:31Z</date>
<key>BackgroundUserBehaviorProcessor</key>
<date>2020-03-15T06:53:11Z</date>
<date>2020-03-21T06:36:35Z</date>
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
<date>2020-03-15T11:59:15Z</date>
<date>2020-03-21T16:03:42Z</date>
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
<date>2020-03-15T06:53:11Z</date>
<date>2020-03-21T06:36:34Z</date>
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
<date>2020-03-15T10:17:08Z</date>
<date>2020-03-21T15:05:31Z</date>
<key>SiriPortraitDonation</key>
<date>2020-03-15T06:53:11Z</date>
<date>2020-03-21T06:36:35Z</date>
</dict>
</plist>

View File

@ -3,8 +3,8 @@
<plist version="1.0">
<dict>
<key>FaceIDModelLastGenerationKey</key>
<date>2020-03-15T06:53:12Z</date>
<date>2020-03-21T06:36:35Z</date>
<key>LastContactClassificationKey</key>
<date>2020-03-15T06:53:13Z</date>
<date>2020-03-21T06:36:38Z</date>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LibrarySchemaVersion</key>
<integer>5001</integer>
<key>MetaSchemaVersion</key>
<integer>3</integer>
</dict>
</plist>

View File

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BlacklistedMeaningsByMeaning</key>
<dict/>
<key>MePersonUUID</key>
<string>39488755-78C0-40B2-B378-EDA280E1823C</string>
<key>SceneWhitelist</key>
<array>
<string>Graduation</string>
<string>Aquarium</string>
<string>Food</string>
<string>Ice Skating</string>
<string>Mountain</string>
<string>Cliff</string>
<string>Basketball</string>
<string>Tennis</string>
<string>Jewelry</string>
<string>Cheese</string>
<string>Softball</string>
<string>Football</string>
<string>Circus</string>
<string>Jet Ski</string>
<string>Playground</string>
<string>Carousel</string>
<string>Paint Ball</string>
<string>Windsurfing</string>
<string>Sailboat</string>
<string>Sunbathing</string>
<string>Dam</string>
<string>Fireplace</string>
<string>Flower</string>
<string>Scuba</string>
<string>Hiking</string>
<string>Cetacean</string>
<string>Pier</string>
<string>Bowling</string>
<string>Snowboarding</string>
<string>Zoo</string>
<string>Snowmobile</string>
<string>Theater</string>
<string>Boat</string>
<string>Casino</string>
<string>Car</string>
<string>Diving</string>
<string>Cycling</string>
<string>Musical Instrument</string>
<string>Board Game</string>
<string>Castle</string>
<string>Sunset Sunrise</string>
<string>Martial Arts</string>
<string>Motocross</string>
<string>Submarine</string>
<string>Cat</string>
<string>Snow</string>
<string>Kiteboarding</string>
<string>Squash</string>
<string>Geyser</string>
<string>Music</string>
<string>Archery</string>
<string>Desert</string>
<string>Blackjack</string>
<string>Fireworks</string>
<string>Sportscar</string>
<string>Feline</string>
<string>Soccer</string>
<string>Museum</string>
<string>Baby</string>
<string>Fencing</string>
<string>Railroad</string>
<string>Nascar</string>
<string>Sky Surfing</string>
<string>Bird</string>
<string>Games</string>
<string>Baseball</string>
<string>Dressage</string>
<string>Snorkeling</string>
<string>Pyramid</string>
<string>Kite</string>
<string>Rowboat</string>
<string>Golf</string>
<string>Watersports</string>
<string>Lightning</string>
<string>Canyon</string>
<string>Auditorium</string>
<string>Night Sky</string>
<string>Karaoke</string>
<string>Skiing</string>
<string>Parade</string>
<string>Forest</string>
<string>Hot Air Balloon</string>
<string>Dragon Parade</string>
<string>Easter Egg</string>
<string>Monument</string>
<string>Jungle</string>
<string>Thanksgiving</string>
<string>Jockey Horse</string>
<string>Stadium</string>
<string>Airplane</string>
<string>Ballet</string>
<string>Yoga</string>
<string>Coral Reef</string>
<string>Skating</string>
<string>Wrestling</string>
<string>Bicycle</string>
<string>Tattoo</string>
<string>Amusement Park</string>
<string>Canoe</string>
<string>Cheerleading</string>
<string>Ping Pong</string>
<string>Fishing</string>
<string>Magic</string>
<string>Reptile</string>
<string>Winter Sport</string>
<string>Waterfall</string>
<string>Train</string>
<string>Bonsai</string>
<string>Surfing</string>
<string>Dog</string>
<string>Cake</string>
<string>Sledding</string>
<string>Sandcastle</string>
<string>Glacier</string>
<string>Lighthouse</string>
<string>Equestrian</string>
<string>Rafting</string>
<string>Shore</string>
<string>Hockey</string>
<string>Santa Claus</string>
<string>Formula One Car</string>
<string>Sport</string>
<string>Vehicle</string>
<string>Boxing</string>
<string>Rollerskating</string>
<string>Underwater</string>
<string>Orchestra</string>
<string>Carnival</string>
<string>Rocket</string>
<string>Skateboarding</string>
<string>Helicopter</string>
<string>Performance</string>
<string>Oktoberfest</string>
<string>Water Polo</string>
<string>Skate Park</string>
<string>Animal</string>
<string>Nightclub</string>
<string>String Instrument</string>
<string>Dinosaur</string>
<string>Gymnastics</string>
<string>Cricket</string>
<string>Volcano</string>
<string>Lake</string>
<string>Aurora</string>
<string>Dancing</string>
<string>Concert</string>
<string>Rock Climbing</string>
<string>Hang Glider</string>
<string>Rodeo</string>
<string>Fish</string>
<string>Art</string>
<string>Motorcycle</string>
<string>Volleyball</string>
<string>Wake Boarding</string>
<string>Badminton</string>
<string>Motor Sport</string>
<string>Sumo</string>
<string>Parasailing</string>
<string>Skydiving</string>
<string>Kickboxing</string>
<string>Pinata</string>
<string>Foosball</string>
<string>Go Kart</string>
<string>Poker</string>
<string>Kayak</string>
<string>Swimming</string>
<string>Atv</string>
<string>Beach</string>
<string>Dartboard</string>
<string>Athletics</string>
<string>Camping</string>
<string>Tornado</string>
<string>Billiards</string>
<string>Rugby</string>
<string>Airshow</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>insertAlbum</key>
<array/>
<key>insertAsset</key>
<array/>
<key>insertHighlight</key>
<array/>
<key>insertMemory</key>
<array/>
<key>insertMoment</key>
<array/>
<key>removeAlbum</key>
<array/>
<key>removeAsset</key>
<array/>
<key>removeHighlight</key>
<array/>
<key>removeMemory</key>
<array/>
<key>removeMoment</key>
<array/>
</dict>
</plist>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>embeddingVersion</key>
<string>1</string>
<key>localeIdentifier</key>
<string>en_US</string>
<key>sceneTaxonomySHA</key>
<string>87914a047c69fbe8013fad2c70fa70c6c03b08b56190fe4054c880e6b9f57cc3</string>
<key>searchIndexVersion</key>
<string>10</string>
</dict>
</plist>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@ -0,0 +1,21 @@
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:exif="http://ns.adobe.com/exif/1.0/"
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
<exif:GPSSpeedRef>K</exif:GPSSpeedRef>
<exif:GPSSpeed>0.0</exif:GPSSpeed>
<exif:GPSTimeStamp>2001-01-01T00:00:00Z</exif:GPSTimeStamp>
<exif:GPSImgDirection>249.12033076703736</exif:GPSImgDirection>
<exif:GPSLongitudeRef>W</exif:GPSLongitudeRef>
<exif:GPSAltitudeRef>0</exif:GPSAltitudeRef>
<exif:GPSLongitude>77.041763833333334</exif:GPSLongitude>
<exif:GPSLatitude>38.917405000000002</exif:GPSLatitude>
<exif:GPSLatitudeRef>N</exif:GPSLatitudeRef>
<exif:GPSImgDirectionRef>T</exif:GPSImgDirectionRef>
<exif:GPSAltitude>41.118671396323769</exif:GPSAltitude>
<exif:GPSHPositioningError>0.0</exif:GPSHPositioningError>
<photoshop:DateCreated>2020-02-04T19:07:38-05:00</photoshop:DateCreated>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@ -0,0 +1,21 @@
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:exif="http://ns.adobe.com/exif/1.0/"
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
<exif:GPSSpeedRef>K</exif:GPSSpeedRef>
<exif:GPSSpeed>0.0</exif:GPSSpeed>
<exif:GPSTimeStamp>2001-01-01T00:00:00Z</exif:GPSTimeStamp>
<exif:GPSImgDirection>296.34306329944246</exif:GPSImgDirection>
<exif:GPSLongitudeRef>W</exif:GPSLongitudeRef>
<exif:GPSAltitudeRef>0</exif:GPSAltitudeRef>
<exif:GPSLongitude>156.44366333333335</exif:GPSLongitude>
<exif:GPSLatitude>20.687278333333332</exif:GPSLatitude>
<exif:GPSLatitudeRef>N</exif:GPSLatitudeRef>
<exif:GPSImgDirectionRef>T</exif:GPSImgDirectionRef>
<exif:GPSAltitude>19.838471419396274</exif:GPSAltitude>
<exif:GPSHPositioningError>0.0</exif:GPSHPositioningError>
<photoshop:DateCreated>2019-09-15T18:37:17-10:00</photoshop:DateCreated>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CollapsedSidebarSectionIdentifiers</key>
<array/>
</dict>
</plist>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BackgroundHighlightCollection</key>
<date>2020-03-21T05:58:14Z</date>
<key>BackgroundHighlightEnrichment</key>
<date>2020-03-21T05:58:14Z</date>
<key>BackgroundJobAssetRevGeocode</key>
<date>2020-03-21T05:58:14Z</date>
<key>BackgroundJobSearch</key>
<date>2020-03-21T05:58:14Z</date>
<key>BackgroundPeopleSuggestion</key>
<date>2020-03-21T05:58:14Z</date>
<key>BackgroundUserBehaviorProcessor</key>
<date>2020-03-21T05:58:14Z</date>
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
<date>2020-03-21T06:37:39Z</date>
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
<date>2020-03-21T05:58:14Z</date>
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
<date>2020-03-21T05:58:15Z</date>
<key>SiriPortraitDonation</key>
<date>2020-03-21T05:58:14Z</date>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>revgeoprovider</key>
<string>7618</string>
</dict>
</plist>

Some files were not shown because too many files have changed in this diff Show More