Added PhotoInfo.place for reverse geolocation data
This commit is contained in:
parent
816b98e617
commit
b338b34d50
60
README.md
60
README.md
@ -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
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.22.23"
|
||||
__version__ = "0.23.0"
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
433
osxphotos/placeinfo.py
Normal 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)
|
||||
@ -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
|
||||
8
setup.py
8
setup.py
@ -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,
|
||||
)
|
||||
|
||||
Binary file not shown.
@ -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>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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>
|
||||
|
||||
Binary file not shown.
@ -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>
|
||||
|
||||
Binary file not shown.
@ -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>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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>
|
||||
@ -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>
|
||||
@ -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.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
@ -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 |
@ -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>
|
||||
@ -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>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
Loading…
x
Reference in New Issue
Block a user