changed interface for export, prepped for exiftool_json_sidecar
This commit is contained in:
@@ -47,7 +47,7 @@
|
|||||||
- [`hidden()`](#hidden)
|
- [`hidden()`](#hidden)
|
||||||
- [`location()`](#location)
|
- [`location()`](#location)
|
||||||
- [`to_json()`](#to_json)
|
- [`to_json()`](#to_json)
|
||||||
- [`export(*args, edited=False, overwrite=False, increment=True)`](#exportargs-editedfalse-overwritefalse-incrementtrue)
|
- [`export(dest, *filename, edited=False, overwrite=False, increment=True)`](#exportdest-filename-editedfalse-overwritefalse-incrementtrue)
|
||||||
+ [Examples](#examples)
|
+ [Examples](#examples)
|
||||||
* [History](#history)
|
* [History](#history)
|
||||||
* [Contributing](#contributing)
|
* [Contributing](#contributing)
|
||||||
@@ -490,10 +490,10 @@ Returns latitude and longitude as a tuple of floats (latitude, longitude). If l
|
|||||||
#### `to_json()`
|
#### `to_json()`
|
||||||
Returns a JSON representation of all photo info
|
Returns a JSON representation of all photo info
|
||||||
|
|
||||||
#### `export(*args, edited=False, overwrite=False, increment=True)`
|
#### `export(dest, *filename, edited=False, overwrite=False, increment=True)`
|
||||||
Export photo from the Photos library to another destination on disk.
|
Export photo from the Photos library to another destination on disk.
|
||||||
- First argument of *args must be valid destination path (or exception raised).
|
- First argument dest must be valid destination path (or exception raised).
|
||||||
- Second argument of *args (optional): name of picture; if not provided, will use current filename
|
- Second argument *filename (optional): name of picture; if not provided, will use current filename
|
||||||
- edited: boolean; if True (default=False), will export the edited version of the photo (or raise exception if no edited version)
|
- edited: boolean; if True (default=False), will export the edited version of the photo (or raise exception if no edited version)
|
||||||
- overwrite: boolean; if True (default=False), will overwrite files if they alreay exist
|
- overwrite: boolean; if True (default=False), will overwrite files if they alreay exist
|
||||||
- increment: boolean; if True (default=True), will increment file name until a non-existant name is found
|
- increment: boolean; if True (default=True), will increment file name until a non-existant name is found
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import logging
|
|||||||
import os.path
|
import os.path
|
||||||
import pathlib
|
import pathlib
|
||||||
import platform
|
import platform
|
||||||
|
import re
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -88,8 +89,8 @@ def _get_os_version():
|
|||||||
|
|
||||||
|
|
||||||
def _check_file_exists(filename):
|
def _check_file_exists(filename):
|
||||||
# returns true if file exists and is not a directory
|
""" returns true if file exists and is not a directory
|
||||||
# otherwise returns false
|
otherwise returns false """
|
||||||
filename = os.path.abspath(filename)
|
filename = os.path.abspath(filename)
|
||||||
return os.path.exists(filename) and not os.path.isdir(filename)
|
return os.path.exists(filename) and not os.path.isdir(filename)
|
||||||
|
|
||||||
@@ -112,6 +113,54 @@ def _get_resource_loc(model_id):
|
|||||||
return folder_id, file_id
|
return folder_id, file_id
|
||||||
|
|
||||||
|
|
||||||
|
def _dd_to_dms(dd):
|
||||||
|
""" convert lat or lon in decimal degrees (dd) to degrees, minutes, seconds """
|
||||||
|
""" return tuple of int(deg), int(min), float(sec) """
|
||||||
|
dd = float(dd)
|
||||||
|
negative = dd < 0
|
||||||
|
dd = abs(dd)
|
||||||
|
min_, sec_ = divmod(dd * 3600, 60)
|
||||||
|
deg_, min_ = divmod(min_, 60)
|
||||||
|
if negative:
|
||||||
|
if deg_ > 0:
|
||||||
|
deg_ = deg_ * -1
|
||||||
|
elif min_ > 0:
|
||||||
|
min_ = min_ * -1
|
||||||
|
else:
|
||||||
|
sec_ = sec_ * -1
|
||||||
|
|
||||||
|
return int(deg_), int(min_), sec_
|
||||||
|
|
||||||
|
|
||||||
|
def dd_to_dms_str(lat, lon):
|
||||||
|
""" convert latitude, longitude in degrees to degrees, minutes, seconds as string """
|
||||||
|
""" lat: latitude in degrees """
|
||||||
|
""" lon: longitude in degrees """
|
||||||
|
""" returns: string tuple in format ("51 deg 30' 12.86\" N", "0 deg 7' 54.50\" W") """
|
||||||
|
""" this is the same format used by exiftool's json format """
|
||||||
|
# TODO: add this to readme
|
||||||
|
|
||||||
|
lat_deg, lat_min, lat_sec = _dd_to_dms(lat)
|
||||||
|
lon_deg, lon_min, lon_sec = _dd_to_dms(lon)
|
||||||
|
|
||||||
|
lat_hemisphere = "N"
|
||||||
|
if any([lat_deg < 0, lat_min < 0, lat_sec < 0]):
|
||||||
|
lat_hemisphere = "S"
|
||||||
|
|
||||||
|
lon_hemisphere = "E"
|
||||||
|
if any([lon_deg < 0, lon_min < 0, lon_sec < 0]):
|
||||||
|
lon_hemisphere = "W"
|
||||||
|
|
||||||
|
lat_str = (
|
||||||
|
f"{abs(lat_deg)} deg {abs(lat_min)}' {abs(lat_sec):.2f}\" {lat_hemisphere}"
|
||||||
|
)
|
||||||
|
lon_str = (
|
||||||
|
f"{abs(lon_deg)} deg {abs(lon_min)}' {abs(lon_sec):.2f}\" {lon_hemisphere}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return lat_str, lon_str
|
||||||
|
|
||||||
|
|
||||||
def get_system_library_path():
|
def get_system_library_path():
|
||||||
""" return the path to the system Photos library as string """
|
""" return the path to the system Photos library as string """
|
||||||
""" only works on MacOS 10.15+ """
|
""" only works on MacOS 10.15+ """
|
||||||
@@ -1464,7 +1513,7 @@ class PhotoInfo:
|
|||||||
""" returns (latitude, longitude) as float in degrees or None """
|
""" returns (latitude, longitude) as float in degrees or None """
|
||||||
return (self._latitude(), self._longitude())
|
return (self._latitude(), self._longitude())
|
||||||
|
|
||||||
def export(self, *args, edited=False, overwrite=False, increment=True):
|
def export(self, dest, *filename, edited=False, overwrite=False, increment=True):
|
||||||
""" export photo """
|
""" export photo """
|
||||||
""" first argument must be valid destination path (or exception raised) """
|
""" first argument must be valid destination path (or exception raised) """
|
||||||
""" second argument (optional): name of picture; if not provided, will use current filename """
|
""" second argument (optional): name of picture; if not provided, will use current filename """
|
||||||
@@ -1478,27 +1527,20 @@ class PhotoInfo:
|
|||||||
# maybe dest, *filename?
|
# maybe dest, *filename?
|
||||||
|
|
||||||
# check arguments and get destination path and filename (if provided)
|
# check arguments and get destination path and filename (if provided)
|
||||||
dest = None # destination path
|
if filename and len(filename) > 2:
|
||||||
filename = None # photo filename
|
|
||||||
if not args:
|
|
||||||
# need at least one arg (destination)
|
|
||||||
raise TypeError("Must pass destination as first argument")
|
|
||||||
else:
|
|
||||||
if len(args) > 2:
|
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"Too many positional arguments. Should be at most two: destination, filename."
|
"Too many positional arguments. Should be at most two: destination, filename."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# verify destination is a valid path
|
# verify destination is a valid path
|
||||||
dest = args[0]
|
|
||||||
if dest is None:
|
if dest is None:
|
||||||
raise ValueError("Destination must not be None")
|
raise ValueError("Destination must not be None")
|
||||||
elif not os.path.isdir(dest):
|
elif not os.path.isdir(dest):
|
||||||
raise FileNotFoundError("Invalid path passed to export")
|
raise FileNotFoundError("Invalid path passed to export")
|
||||||
|
|
||||||
if len(args) == 2:
|
if filename and len(filename) == 1:
|
||||||
# second arg is filename of picture
|
# second arg is filename of picture
|
||||||
filename = args[1]
|
filename = filename[0]
|
||||||
else:
|
else:
|
||||||
# no filename provided so use the default
|
# no filename provided so use the default
|
||||||
# if edited file requested, use filename but add _edited
|
# if edited file requested, use filename but add _edited
|
||||||
@@ -1584,6 +1626,66 @@ class PhotoInfo:
|
|||||||
|
|
||||||
return str(dest)
|
return str(dest)
|
||||||
|
|
||||||
|
def _exiftool_json_sidecar(self):
|
||||||
|
""" return json string of EXIF details in exiftool sidecar format """
|
||||||
|
exif = {}
|
||||||
|
exif["FileName"] = self.filename()
|
||||||
|
|
||||||
|
if self.description():
|
||||||
|
exif["ImageDescription"] = self.description()
|
||||||
|
exif["Description"] = self.description()
|
||||||
|
|
||||||
|
if self.title():
|
||||||
|
exif["Title"] = self.title()
|
||||||
|
|
||||||
|
if self.keywords():
|
||||||
|
exif["TagsList"] = exif["Keywords"] = self.keywords()
|
||||||
|
|
||||||
|
if self.persons():
|
||||||
|
exif["PersonInImage"] = self.persons()
|
||||||
|
|
||||||
|
# if self.favorite():
|
||||||
|
# exif["Rating"] = 5
|
||||||
|
|
||||||
|
(lat, lon) = self.location()
|
||||||
|
if lat is not None and lon is not None:
|
||||||
|
lat_str, lon_str = dd_to_dms_str(lat, lon)
|
||||||
|
exif["GPSLatitude"] = lat_str
|
||||||
|
exif["GPSLongitude"] = lon_str
|
||||||
|
exif["GPSPosition"] = f"{lat_str}, {lon_str}"
|
||||||
|
lat_ref = "North" if lat >= 0 else "South"
|
||||||
|
lon_ref = "East" if lon >= 0 else "West"
|
||||||
|
exif["GPSLatitudeRef"] = lat_ref
|
||||||
|
exif["GPSLongitudeRef"] = lon_ref
|
||||||
|
|
||||||
|
# process date/time and timezone offset
|
||||||
|
date = self.date()
|
||||||
|
# exiftool expects format to "2015:01:18 12:00:00"
|
||||||
|
datetimeoriginal = date.strftime("%Y:%m:%d %H:%M:%S")
|
||||||
|
offsettime = date.strftime("%z")
|
||||||
|
# find timezone offset in format "-04:00"
|
||||||
|
offset = re.findall(r"([+-]?)([\d]{2})([\d]{2})", offsettime)
|
||||||
|
offset = offset[0] # findall returns list of tuples
|
||||||
|
offsettime = f"{offset[0]}{offset[1]}:{offset[2]}"
|
||||||
|
exif["DateTimeOriginal"] = datetimeoriginal
|
||||||
|
exif["OffsetTimeOriginal"] = offsettime
|
||||||
|
|
||||||
|
json_str = json.dumps([exif])
|
||||||
|
return json_str
|
||||||
|
|
||||||
|
def _write_sidecar_car(self, filename, json_str):
|
||||||
|
if not filename and not json_str:
|
||||||
|
raise (
|
||||||
|
ValueError(
|
||||||
|
f"filename {filename} and json_str {json_str} must not be None"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: catch exception?
|
||||||
|
f = open(filename, "w")
|
||||||
|
f.write(json_str)
|
||||||
|
f.close()
|
||||||
|
|
||||||
def _longitude(self):
|
def _longitude(self):
|
||||||
""" Returns longitude, in degrees """
|
""" Returns longitude, in degrees """
|
||||||
return self._info["longitude"]
|
return self._info["longitude"]
|
||||||
|
|||||||
Binary file not shown.
@@ -5,7 +5,7 @@
|
|||||||
<key>LithiumMessageTracer</key>
|
<key>LithiumMessageTracer</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>LastReportedDate</key>
|
<key>LastReportedDate</key>
|
||||||
<date>2019-08-24T02:50:48Z</date>
|
<date>2019-12-08T16:44:38Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
<key>PXPeopleScreenUnlocked</key>
|
<key>PXPeopleScreenUnlocked</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||||
<date>2019-12-07T16:40:40Z</date>
|
<date>2019-12-16T02:55:50Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||||
<date>2019-12-07T16:40:41Z</date>
|
<date>2019-12-16T02:55:50Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -11,6 +11,6 @@
|
|||||||
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
|
<key>PLLastRevGeoForcedProviderOutOfDateCheckVersionKey</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
<key>PLLastRevGeoVerFileFetchDateKey</key>
|
<key>PLLastRevGeoVerFileFetchDateKey</key>
|
||||||
<date>2019-12-07T16:40:32Z</date>
|
<date>2019-12-13T18:43:07Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,7 +7,7 @@
|
|||||||
<key>hostuuid</key>
|
<key>hostuuid</key>
|
||||||
<string>9575E48B-8D5F-5654-ABAC-4431B1167324</string>
|
<string>9575E48B-8D5F-5654-ABAC-4431B1167324</string>
|
||||||
<key>pid</key>
|
<key>pid</key>
|
||||||
<integer>423</integer>
|
<integer>1794</integer>
|
||||||
<key>processname</key>
|
<key>processname</key>
|
||||||
<string>photolibraryd</string>
|
<string>photolibraryd</string>
|
||||||
<key>uid</key>
|
<key>uid</key>
|
||||||
|
|||||||
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.
@@ -3,24 +3,24 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>BackgroundHighlightCollection</key>
|
<key>BackgroundHighlightCollection</key>
|
||||||
<date>2019-12-14T18:19:30Z</date>
|
<date>2019-12-15T18:49:56Z</date>
|
||||||
<key>BackgroundHighlightEnrichment</key>
|
<key>BackgroundHighlightEnrichment</key>
|
||||||
<date>2019-12-14T18:19:29Z</date>
|
<date>2019-12-15T18:49:35Z</date>
|
||||||
<key>BackgroundJobAssetRevGeocode</key>
|
<key>BackgroundJobAssetRevGeocode</key>
|
||||||
<date>2019-12-14T18:19:30Z</date>
|
<date>2019-12-15T20:55:19Z</date>
|
||||||
<key>BackgroundJobSearch</key>
|
<key>BackgroundJobSearch</key>
|
||||||
<date>2019-12-14T18:19:30Z</date>
|
<date>2019-12-15T18:49:56Z</date>
|
||||||
<key>BackgroundPeopleSuggestion</key>
|
<key>BackgroundPeopleSuggestion</key>
|
||||||
<date>2019-12-14T18:19:28Z</date>
|
<date>2019-12-15T18:49:35Z</date>
|
||||||
<key>BackgroundUserBehaviorProcessor</key>
|
<key>BackgroundUserBehaviorProcessor</key>
|
||||||
<date>0000-12-30T00:00:00Z</date>
|
<date>2019-12-15T18:49:56Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
|
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
|
||||||
<date>2019-12-14T18:19:28Z</date>
|
<date>2019-12-15T20:55:19Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
|
||||||
<date>2019-12-14T18:19:28Z</date>
|
<date>2019-12-15T18:49:35Z</date>
|
||||||
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
|
||||||
<date>2019-12-10T06:45:58Z</date>
|
<date>2019-12-15T20:55:19Z</date>
|
||||||
<key>SiriPortraitDonation</key>
|
<key>SiriPortraitDonation</key>
|
||||||
<date>0000-12-30T00:00:00Z</date>
|
<date>2019-12-15T18:49:56Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Binary file not shown.
@@ -3,8 +3,8 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>FaceIDModelLastGenerationKey</key>
|
<key>FaceIDModelLastGenerationKey</key>
|
||||||
<date>2019-12-10T06:45:58Z</date>
|
<date>2019-12-15T18:49:56Z</date>
|
||||||
<key>LastContactClassificationKey</key>
|
<key>LastContactClassificationKey</key>
|
||||||
<date>2019-12-10T06:46:00Z</date>
|
<date>2019-12-15T18:49:58Z</date>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>IncrementalPersonProcessingStage</key>
|
<key>IncrementalPersonProcessingStage</key>
|
||||||
<integer>6</integer>
|
<integer>0</integer>
|
||||||
<key>PersonBuilderLastMinimumFaceGroupSizeForCreatingMergeCandidates</key>
|
<key>PersonBuilderLastMinimumFaceGroupSizeForCreatingMergeCandidates</key>
|
||||||
<integer>15</integer>
|
<integer>15</integer>
|
||||||
<key>PersonBuilderMergeCandidatesEnabled</key>
|
<key>PersonBuilderMergeCandidatesEnabled</key>
|
||||||
|
|||||||
Binary file not shown.
@@ -57,6 +57,7 @@ UUID_DICT = {
|
|||||||
"export": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg"
|
"export": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_export_1():
|
def test_export_1():
|
||||||
# test basic export
|
# test basic export
|
||||||
# get an unedited image and export it using default filename
|
# get an unedited image and export it using default filename
|
||||||
@@ -390,3 +391,75 @@ def test_export_13():
|
|||||||
with pytest.raises(Exception) as e:
|
with pytest.raises(Exception) as e:
|
||||||
assert photos[0].export(dest)
|
assert photos[0].export(dest)
|
||||||
assert e.type == type(FileNotFoundError())
|
assert e.type == type(FileNotFoundError())
|
||||||
|
|
||||||
|
|
||||||
|
def test_dd_to_dms_str_1():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
lat_str, lon_str = osxphotos.dd_to_dms_str(
|
||||||
|
34.559331096, 69.206499174
|
||||||
|
) # Kabul, 34°33'33.59" N 69°12'23.40" E
|
||||||
|
|
||||||
|
assert lat_str == "34 deg 33' 33.59\" N"
|
||||||
|
assert lon_str == "69 deg 12' 23.40\" E"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dd_to_dms_str_2():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
lat_str, lon_str = osxphotos.dd_to_dms_str(
|
||||||
|
-34.601997592, -58.375665164
|
||||||
|
) # Buenos Aires, 34°36'7.19" S 58°22'32.39" W
|
||||||
|
|
||||||
|
assert lat_str == "34 deg 36' 7.19\" S"
|
||||||
|
assert lon_str == "58 deg 22' 32.39\" W"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dd_to_dms_str_3():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
lat_str, lon_str = osxphotos.dd_to_dms_str(
|
||||||
|
-1.2666656, 36.7999968
|
||||||
|
) # Nairobi, 1°15'60.00" S 36°47'59.99" E
|
||||||
|
|
||||||
|
assert lat_str == "1 deg 15' 60.00\" S"
|
||||||
|
assert lon_str == "36 deg 47' 59.99\" E"
|
||||||
|
|
||||||
|
|
||||||
|
def test_dd_to_dms_str_4():
|
||||||
|
import osxphotos
|
||||||
|
|
||||||
|
lat_str, lon_str = osxphotos.dd_to_dms_str(
|
||||||
|
38.889248, -77.050636
|
||||||
|
) # DC: 38° 53' 21.2928" N, 77° 3' 2.2896" W
|
||||||
|
|
||||||
|
assert lat_str == "38 deg 53' 21.29\" N"
|
||||||
|
assert lon_str == "77 deg 3' 2.29\" W"
|
||||||
|
|
||||||
|
|
||||||
|
def test_exiftool_json_sidecar():
|
||||||
|
import osxphotos
|
||||||
|
import json
|
||||||
|
|
||||||
|
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||||
|
photos = photosdb.photos(uuid=[UUID_DICT["location"]])
|
||||||
|
|
||||||
|
json_expected = json.loads(
|
||||||
|
"""
|
||||||
|
[{"FileName": "DC99FBDD-7A52-4100-A5BB-344131646C30.jpeg",
|
||||||
|
"Title": "St. James\'s Park",
|
||||||
|
"TagsList": ["London 2018", "St. James\'s Park", "England", "United Kingdom", "UK", "London"],
|
||||||
|
"Keywords": ["London 2018", "St. James\'s Park", "England", "United Kingdom", "UK", "London"],
|
||||||
|
"GPSLatitude": "51 deg 30\' 12.86\\" N",
|
||||||
|
"GPSLongitude": "0 deg 7\' 54.50\\" W",
|
||||||
|
"GPSPosition": "51 deg 30\' 12.86\\" N, 0 deg 7\' 54.50\\" W",
|
||||||
|
"GPSLatitudeRef": "North", "GPSLongitudeRef": "West",
|
||||||
|
"DateTimeOriginal": "2018:10:13 09:18:12", "OffsetTimeOriginal": "-04:00"}]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
json_got = photos[0]._exiftool_json_sidecar()
|
||||||
|
json_got = json.loads(json_got)
|
||||||
|
|
||||||
|
assert sorted(json_got[0].items()) == sorted(json_expected[0].items())
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user