parent
091f1d9bb4
commit
603dabb8f4
15
README.md
15
README.md
@ -1294,7 +1294,8 @@ exiftool must be installed in the path for this to work. If exiftool cannot be
|
||||
|
||||
`ExifTool` provides the following methods:
|
||||
|
||||
- `as_dict()`: returns all EXIF metadata found in the file as a dictionary in following form (Note: this shows just a subset of available metadata). See [exiftool](https://exiftool.org/) documentation to understand which metadata keys are available.
|
||||
- `asdict()`: returns all EXIF metadata found in the file as a dictionary in following form (Note: this shows just a subset of available metadata). See [exiftool](https://exiftool.org/) documentation to understand which metadata keys are available.
|
||||
|
||||
```python
|
||||
{'Composite:Aperture': 2.2,
|
||||
'Composite:GPSPosition': '-34.9188916666667 138.596861111111',
|
||||
@ -1307,7 +1308,7 @@ exiftool must be installed in the path for this to work. If exiftool cannot be
|
||||
}
|
||||
```
|
||||
|
||||
- `json()`: returns same information as `as_dict()` but as a serialized JSON string.
|
||||
- `json()`: returns same information as `asdict()` but as a serialized JSON string.
|
||||
|
||||
- `setvalue(tag, value)`: write to the EXIF data in the photo file. To delete a tag, use setvalue with value = `None`. For example:
|
||||
```python
|
||||
@ -1318,7 +1319,7 @@ photo.exiftool.setvalue("XMP:Title", "Title of photo")
|
||||
photo.exiftool.addvalues("IPTC:Keywords", "vacation", "beach")
|
||||
```
|
||||
|
||||
**Caution**: I caution against writing new EXIF data to photos in the Photos library because this will overwrite the original copy of the photo and could adversely affect how Photos behaves. `exiftool.as_dict()` is useful for getting access to all the photos information but if you want to write new EXIF data, I recommend you export the photo first then write the data. [PhotoInfo.export()](#export) does this if called with `exiftool=True`.
|
||||
**Caution**: I caution against writing new EXIF data to photos in the Photos library because this will overwrite the original copy of the photo and could adversely affect how Photos behaves. `exiftool.asdict()` is useful for getting access to all the photos information but if you want to write new EXIF data, I recommend you export the photo first then write the data. [PhotoInfo.export()](#export) does this if called with `exiftool=True`.
|
||||
|
||||
#### `score`
|
||||
Returns a [ScoreInfo](#scoreinfo) data class object which provides access to the computed aesthetic scores for each photo.
|
||||
@ -1326,7 +1327,10 @@ Returns a [ScoreInfo](#scoreinfo) data class object which provides access to the
|
||||
**Note**: Valid only for Photos 5; returns None for earlier Photos versions.
|
||||
|
||||
#### `json()`
|
||||
Returns a JSON representation of all photo info
|
||||
Returns a JSON representation of all photo info.
|
||||
|
||||
#### `asdict()`
|
||||
Returns a dictionary representation of all photo info.
|
||||
|
||||
#### `export()`
|
||||
`export(dest, *filename, edited=False, live_photo=False, export_as_hardlink=False, overwrite=False, increment=True, sidecar_json=False, sidecar_xmp=False, use_photos_export=False, timeout=120, exiftool=False, no_xattr=False, use_albums_as_keywords=False, use_persons_as_keywords=False)`
|
||||
@ -1674,6 +1678,9 @@ Returns a list of [FaceInfo](#faceinfo) objects associated with this person sort
|
||||
#### `json()`
|
||||
Returns a json string representation of the PersonInfo instance.
|
||||
|
||||
#### `asdict()`
|
||||
Returns a dictionary representation of the PersonInfo instance.
|
||||
|
||||
### FaceInfo
|
||||
[PhotoInfo.face_info](#photofaceinfo) return a list of FaceInfo objects representing detected faces in a photo. The FaceInfo class has the following properties and methods.
|
||||
|
||||
|
||||
@ -228,7 +228,7 @@ class ExifTool:
|
||||
ver = self.run_commands("-ver", no_file=True)
|
||||
return ver.decode("utf-8")
|
||||
|
||||
def as_dict(self):
|
||||
def asdict(self):
|
||||
""" return dictionary of all EXIF tags and values from exiftool
|
||||
returns empty dict if no tags
|
||||
"""
|
||||
@ -245,7 +245,7 @@ class ExifTool:
|
||||
|
||||
def _read_exif(self):
|
||||
""" read exif data from file """
|
||||
data = self.as_dict()
|
||||
data = self.asdict()
|
||||
self.data = {k: v for k, v in data.items()}
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@ -66,10 +66,10 @@ class PersonInfo:
|
||||
# no faces
|
||||
return []
|
||||
|
||||
def json(self):
|
||||
""" Returns JSON representation of class instance """
|
||||
def asdict(self):
|
||||
""" Returns dictionary representation of class instance """
|
||||
keyphoto = self.keyphoto.uuid if self.keyphoto is not None else None
|
||||
person = {
|
||||
return {
|
||||
"uuid": self.uuid,
|
||||
"name": self.name,
|
||||
"displayname": self.display_name,
|
||||
@ -77,7 +77,10 @@ class PersonInfo:
|
||||
"facecount": self.facecount,
|
||||
"keyphoto": keyphoto,
|
||||
}
|
||||
return json.dumps(person)
|
||||
|
||||
def json(self):
|
||||
""" Returns JSON representation of class instance """
|
||||
return json.dumps(self.asdict())
|
||||
|
||||
def __str__(self):
|
||||
return f"PersonInfo(name={self.name}, display_name={self.display_name}, uuid={self.uuid}, facecount={self.facecount})"
|
||||
|
||||
@ -5,6 +5,7 @@ PhotosDB.photos() returns a list of PhotoInfo objects
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@ -950,22 +951,23 @@ class PhotoInfo:
|
||||
}
|
||||
return yaml.dump(info, sort_keys=False)
|
||||
|
||||
def json(self):
|
||||
""" return JSON representation """
|
||||
def asdict(self):
|
||||
""" return dict representation """
|
||||
|
||||
date_modified_iso = (
|
||||
self.date_modified.isoformat() if self.date_modified else None
|
||||
)
|
||||
folders = {album.title: album.folder_names for album in self.album_info}
|
||||
exif = dataclasses.asdict(self.exif_info) if self.exif_info else {}
|
||||
place = self.place.as_dict() if self.place else {}
|
||||
place = self.place.asdict() if self.place else {}
|
||||
score = dataclasses.asdict(self.score) if self.score else {}
|
||||
comments = [comment.asdict() for comment in self.comments]
|
||||
likes = [like.asdict() for like in self.likes]
|
||||
faces = [face.asdict() for face in self.face_info]
|
||||
|
||||
pic = {
|
||||
return {
|
||||
"library": self._db._library_path,
|
||||
"uuid": self.uuid,
|
||||
"filename": self.filename,
|
||||
"original_filename": self.original_filename,
|
||||
"date": self.date.isoformat(),
|
||||
"date": self.date,
|
||||
"description": self.description,
|
||||
"title": self.title,
|
||||
"keywords": self.keywords,
|
||||
@ -974,6 +976,7 @@ class PhotoInfo:
|
||||
"albums": self.albums,
|
||||
"folders": folders,
|
||||
"persons": self.persons,
|
||||
"faces": faces,
|
||||
"path": self.path,
|
||||
"ismissing": self.ismissing,
|
||||
"hasadjustments": self.hasadjustments,
|
||||
@ -987,12 +990,13 @@ class PhotoInfo:
|
||||
"isphoto": self.isphoto,
|
||||
"ismovie": self.ismovie,
|
||||
"uti": self.uti,
|
||||
"uti_original": self.uti_original,
|
||||
"burst": self.burst,
|
||||
"live_photo": self.live_photo,
|
||||
"path_live_photo": self.path_live_photo,
|
||||
"iscloudasset": self.iscloudasset,
|
||||
"incloud": self.incloud,
|
||||
"date_modified": date_modified_iso,
|
||||
"date_modified": self.date_modified,
|
||||
"portrait": self.portrait,
|
||||
"screenshot": self.screenshot,
|
||||
"slow_mo": self.slow_mo,
|
||||
@ -1001,6 +1005,8 @@ class PhotoInfo:
|
||||
"selfie": self.selfie,
|
||||
"panorama": self.panorama,
|
||||
"has_raw": self.has_raw,
|
||||
"israw": self.israw,
|
||||
"raw_original": self.raw_original,
|
||||
"uti_raw": self.uti_raw,
|
||||
"path_raw": self.path_raw,
|
||||
"place": place,
|
||||
@ -1014,8 +1020,17 @@ class PhotoInfo:
|
||||
"original_width": self.original_width,
|
||||
"original_orientation": self.original_orientation,
|
||||
"original_filesize": self.original_filesize,
|
||||
"comments": comments,
|
||||
"likes": likes,
|
||||
}
|
||||
return json.dumps(pic)
|
||||
|
||||
def json(self):
|
||||
""" Return JSON representation """
|
||||
def default(o):
|
||||
if isinstance(o, (datetime.date, datetime.datetime)):
|
||||
return o.isoformat()
|
||||
|
||||
return json.dumps(self.asdict(), sort_keys=True, default=default)
|
||||
|
||||
def __eq__(self, other):
|
||||
""" Compare two PhotoInfo objects for equality """
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
""" PhotosDB method for processing comments and likes on shared photos.
|
||||
Do not import this module directly """
|
||||
|
||||
import dataclasses
|
||||
import datetime
|
||||
from dataclasses import dataclass
|
||||
|
||||
@ -30,6 +31,9 @@ class CommentInfo:
|
||||
ismine: bool
|
||||
text: str
|
||||
|
||||
def asdict(self):
|
||||
return dataclasses.asdict(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LikeInfo:
|
||||
@ -39,6 +43,9 @@ class LikeInfo:
|
||||
user: str
|
||||
ismine: bool
|
||||
|
||||
def asdict(self):
|
||||
return dataclasses.asdict(self)
|
||||
|
||||
|
||||
# The following methods do not get imported into PhotosDB
|
||||
# but will get called by _process_comments
|
||||
|
||||
@ -491,7 +491,7 @@ class PlaceInfo4(PlaceInfo):
|
||||
}
|
||||
return "PlaceInfo(" + ", ".join([f"{k}='{v}'" for k, v in info.items()]) + ")"
|
||||
|
||||
def as_dict(self):
|
||||
def asdict(self):
|
||||
return {
|
||||
"name": self.name,
|
||||
"names": self.names._asdict(),
|
||||
@ -634,7 +634,7 @@ class PlaceInfo5(PlaceInfo):
|
||||
}
|
||||
return "PlaceInfo(" + ", ".join([f"{k}='{v}'" for k, v in info.items()]) + ")"
|
||||
|
||||
def as_dict(self):
|
||||
def asdict(self):
|
||||
return {
|
||||
"name": self.name,
|
||||
"names": self.names._asdict(),
|
||||
|
||||
@ -133,6 +133,7 @@ RawInfo = namedtuple(
|
||||
"uti_raw",
|
||||
],
|
||||
)
|
||||
|
||||
RAW_DICT = {
|
||||
"D05A5FE3-15FB-49A1-A15D-AB3DA6F8B068": RawInfo(
|
||||
"raw image, no jpeg pair",
|
||||
@ -181,6 +182,7 @@ RAW_DICT = {
|
||||
def photosdb():
|
||||
return osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
|
||||
|
||||
def test_init1():
|
||||
# test named argument
|
||||
|
||||
@ -922,7 +924,6 @@ def test_from_to_date(photosdb):
|
||||
os.environ["TZ"] = "US/Pacific"
|
||||
time.tzset()
|
||||
|
||||
|
||||
photos = photosdb.photos(from_date=datetime.datetime(2018, 10, 28))
|
||||
assert len(photos) == 7
|
||||
|
||||
@ -941,7 +942,6 @@ def test_from_to_date_tz(photosdb):
|
||||
os.environ["TZ"] = "US/Pacific"
|
||||
time.tzset()
|
||||
|
||||
|
||||
photos = photosdb.photos(
|
||||
from_date=datetime.datetime(2018, 9, 28, 13, 7, 0),
|
||||
to_date=datetime.datetime(2018, 9, 28, 13, 9, 0),
|
||||
@ -978,8 +978,7 @@ def test_date_invalid():
|
||||
# doesn't run correctly with the module-level fixture
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import osxphotos
|
||||
|
||||
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photos = photosdb.photos(uuid=[UUID_DICT["date_invalid"]])
|
||||
assert len(photos) == 1
|
||||
|
||||
@ -854,7 +854,7 @@ def test_export_exiftool():
|
||||
files = glob.glob("*")
|
||||
assert sorted(files) == sorted([CLI_EXIFTOOL[uuid]["File:FileName"]])
|
||||
|
||||
exif = ExifTool(CLI_EXIFTOOL[uuid]["File:FileName"]).as_dict()
|
||||
exif = ExifTool(CLI_EXIFTOOL[uuid]["File:FileName"]).asdict()
|
||||
for key in CLI_EXIFTOOL[uuid]:
|
||||
assert exif[key] == CLI_EXIFTOOL[uuid][key]
|
||||
|
||||
|
||||
@ -53,6 +53,23 @@ LIKE_UUID_DICT = {
|
||||
],
|
||||
}
|
||||
|
||||
COMMENT_UUID_ASDICT = {
|
||||
"4E4944A0-3E5C-4028-9600-A8709F2FA1DB": {
|
||||
"datetime": datetime.datetime(2020, 9, 19, 22, 54, 12, 947978),
|
||||
"user": None,
|
||||
"ismine": True,
|
||||
"text": "Nice trophy",
|
||||
}
|
||||
}
|
||||
|
||||
LIKE_UUID_ASDICT = {
|
||||
"65BADBD7-A50C-4956-96BA-1BB61155DA17": {
|
||||
"datetime": datetime.datetime(2020, 9, 18, 10, 28, 52, 570000),
|
||||
"user": None,
|
||||
"ismine": False,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def photosdb():
|
||||
@ -69,3 +86,15 @@ def test_likes(photosdb):
|
||||
for uuid in LIKE_UUID_DICT:
|
||||
photo = photosdb.get_photo(uuid)
|
||||
assert photo.likes == LIKE_UUID_DICT[uuid]
|
||||
|
||||
|
||||
def test_comments_as_dict(photosdb):
|
||||
for uuid in COMMENT_UUID_ASDICT:
|
||||
photo = photosdb.get_photo(uuid)
|
||||
assert photo.comments[0].asdict() == COMMENT_UUID_ASDICT[uuid]
|
||||
|
||||
|
||||
def test_likes_as_dict(photosdb):
|
||||
for uuid in LIKE_UUID_ASDICT:
|
||||
photo = photosdb.get_photo(uuid)
|
||||
assert photo.likes[0].asdict() == LIKE_UUID_ASDICT[uuid]
|
||||
|
||||
@ -198,7 +198,7 @@ def test_as_dict():
|
||||
import osxphotos.exiftool
|
||||
|
||||
exif1 = osxphotos.exiftool.ExifTool(TEST_FILE_ONE_KEYWORD)
|
||||
exifdata = exif1.as_dict()
|
||||
exifdata = exif1.asdict()
|
||||
assert exifdata["XMP:TagsList"] == "wedding"
|
||||
|
||||
|
||||
@ -227,7 +227,7 @@ def test_photoinfo_exiftool():
|
||||
for uuid in EXIF_UUID:
|
||||
photo = photosdb.photos(uuid=[uuid])[0]
|
||||
exiftool = photo.exiftool
|
||||
exif_dict = exiftool.as_dict()
|
||||
exif_dict = exiftool.asdict()
|
||||
for key, val in EXIF_UUID[uuid].items():
|
||||
assert exif_dict[key] == val
|
||||
|
||||
|
||||
@ -130,15 +130,15 @@ def test_place_no_place_info():
|
||||
assert photo.place is None
|
||||
|
||||
|
||||
def test_place_place_info_as_dict():
|
||||
# test PlaceInfo.as_dict()
|
||||
def test_place_place_info_asdict():
|
||||
# test PlaceInfo.asdict()
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photo = photosdb.photos(uuid=[UUID_DICT["place_maui"]])[0]
|
||||
|
||||
assert isinstance(photo.place, osxphotos.placeinfo.PlaceInfo)
|
||||
assert photo.place.as_dict() == MAUI_DICT
|
||||
assert photo.place.asdict() == MAUI_DICT
|
||||
|
||||
|
||||
# def test_place_str():
|
||||
|
||||
@ -89,11 +89,11 @@ def test_place_str():
|
||||
|
||||
|
||||
def test_place_as_dict():
|
||||
# test PlaceInfo.as_dict()
|
||||
# test PlaceInfo.asdict()
|
||||
import osxphotos
|
||||
|
||||
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
|
||||
photo = photosdb.photos(uuid=[UUID_DICT["place_uk"]])[0]
|
||||
assert photo.place is not None
|
||||
assert isinstance(photo.place, osxphotos.placeinfo.PlaceInfo)
|
||||
assert photo.place.as_dict() == UK_DICT
|
||||
assert photo.place.asdict() == UK_DICT
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user