Compare commits

...

5 Commits

Author SHA1 Message Date
Rhet Turnbull
0cce234a8c Fixed handling of date_modified for Catalina, issue #247 2020-10-31 08:46:35 -07:00
Rhet Turnbull
c5dba8c89b Added --has-comment/--has-likes to CLI, issue #240 2020-10-29 21:34:15 -07:00
Rhet Turnbull
603dabb8f4 Cleaned up as_dict/asdict, issue #144, #188 2020-10-27 06:54:42 -07:00
Rhet Turnbull
091f1d9bb4 Updated CHANGELOG.md 2020-10-25 22:25:18 -07:00
Rhet Turnbull
d16932d0fd Updated README.md 2020-10-25 22:24:47 -07:00
323 changed files with 1971 additions and 368 deletions

View File

@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v0.36.0](https://github.com/RhetTbull/osxphotos/compare/v0.35.7...v0.36.0)
> 26 October 2020
- Added verbose to PhotosDB(), partial fix for #110 [`d87b8f3`](https://github.com/RhetTbull/osxphotos/commit/d87b8f30a45cbb6fdb315a12f8585e2bdc21be6b)
- Added comments/likes, implements #214 [`23de6b5`](https://github.com/RhetTbull/osxphotos/commit/23de6b58908371d9ca55d1d1999c6d56de454180)
- Cleaned up constructor for PhotosDB [`667c89e`](https://github.com/RhetTbull/osxphotos/commit/667c89e32c3f96baeafebc03e83517ea05693b00)
#### [v0.35.7](https://github.com/RhetTbull/osxphotos/compare/v0.35.6...v0.35.7) #### [v0.35.7](https://github.com/RhetTbull/osxphotos/compare/v0.35.6...v0.35.7)
> 24 October 2020 > 24 October 2020

View File

@@ -221,6 +221,10 @@ Options:
2000-01-12T12:00:00, 2000-01-12T12:00:00,
2001-01-12T12:00:00-07:00, or 2000-12-31 2001-01-12T12:00:00-07:00, or 2000-12-31
(ISO 8601). (ISO 8601).
--has-comment Search for photos that have comments.
--no-comment Search for photos with no comments.
--has-likes Search for photos that have likes.
--no-likes Search for photos with no likes.
--deleted Include photos from the 'Recently Deleted' --deleted Include photos from the 'Recently Deleted'
folder. folder.
--deleted-only Include only photos from the 'Recently --deleted-only Include only photos from the 'Recently
@@ -423,23 +427,24 @@ Substitution Description
{descr} Description of the photo {descr} Description of the photo
{created.date} Photo's creation date in ISO format, e.g. {created.date} Photo's creation date in ISO format, e.g.
'2020-03-22' '2020-03-22'
{created.year} 4-digit year of file creation time {created.year} 4-digit year of photo creation time
{created.yy} 2-digit year of file creation time {created.yy} 2-digit year of photo creation time
{created.mm} 2-digit month of the file creation time {created.mm} 2-digit month of the photo creation time
(zero padded) (zero padded)
{created.month} Month name in user's locale of the file {created.month} Month name in user's locale of the photo
creation time creation time
{created.mon} Month abbreviation in the user's locale of {created.mon} Month abbreviation in the user's locale of
the file creation time the photo creation time
{created.dd} 2-digit day of the month (zero padded) of {created.dd} 2-digit day of the month (zero padded) of
file creation time photo creation time
{created.dow} Day of week in user's locale of the file {created.dow} Day of week in user's locale of the photo
creation time creation time
{created.doy} 3-digit day of year (e.g Julian day) of file {created.doy} 3-digit day of year (e.g Julian day) of
creation time, starting from 1 (zero padded) photo creation time, starting from 1 (zero
{created.hour} 2-digit hour of the file creation time padded)
{created.min} 2-digit minute of the file creation time {created.hour} 2-digit hour of the photo creation time
{created.sec} 2-digit second of the file creation time {created.min} 2-digit minute of the photo creation time
{created.sec} 2-digit second of the photo creation time
{created.strftime} Apply strftime template to file creation {created.strftime} Apply strftime template to file creation
date/time. Should be used in form date/time. Should be used in form
{created.strftime,TEMPLATE} where TEMPLATE {created.strftime,TEMPLATE} where TEMPLATE
@@ -451,22 +456,26 @@ Substitution Description
templates. templates.
{modified.date} Photo's modification date in ISO format, {modified.date} Photo's modification date in ISO format,
e.g. '2020-03-22' e.g. '2020-03-22'
{modified.year} 4-digit year of file modification time {modified.year} 4-digit year of photo modification time
{modified.yy} 2-digit year of file modification time {modified.yy} 2-digit year of photo modification time
{modified.mm} 2-digit month of the file modification time {modified.mm} 2-digit month of the photo modification time
(zero padded) (zero padded)
{modified.month} Month name in user's locale of the file {modified.month} Month name in user's locale of the photo
modification time modification time
{modified.mon} Month abbreviation in the user's locale of {modified.mon} Month abbreviation in the user's locale of
the file modification time the photo modification time
{modified.dd} 2-digit day of the month (zero padded) of {modified.dd} 2-digit day of the month (zero padded) of
the file modification time the photo modification time
{modified.doy} 3-digit day of year (e.g Julian day) of file {modified.dow} Day of week in user's locale of the photo
modification time, starting from 1 (zero modification time
padded) {modified.doy} 3-digit day of year (e.g Julian day) of
{modified.hour} 2-digit hour of the file modification time photo modification time, starting from 1
{modified.min} 2-digit minute of the file modification time (zero padded)
{modified.sec} 2-digit second of the file modification time {modified.hour} 2-digit hour of the photo modification time
{modified.min} 2-digit minute of the photo modification
time
{modified.sec} 2-digit second of the photo modification
time
{today.date} Current date in iso format, e.g. {today.date} Current date in iso format, e.g.
'2020-03-22' '2020-03-22'
{today.year} 4-digit year of current date {today.year} 4-digit year of current date
@@ -542,6 +551,7 @@ Substitution Description
(Photos 5 only) (Photos 5 only)
{label_normalized} All lower case version of 'label' (Photos 5 only) {label_normalized} All lower case version of 'label' (Photos 5 only)
{comment} Comment(s) on shared Photos; format is 'Person name: {comment} Comment(s) on shared Photos; format is 'Person name:
comment text' (Photos 5 only)
``` ```
Example: export all photos to ~/Desktop/export group in folders by date created Example: export all photos to ~/Desktop/export group in folders by date created
@@ -1294,7 +1304,8 @@ exiftool must be installed in the path for this to work. If exiftool cannot be
`ExifTool` provides the following methods: `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 ```python
{'Composite:Aperture': 2.2, {'Composite:Aperture': 2.2,
'Composite:GPSPosition': '-34.9188916666667 138.596861111111', 'Composite:GPSPosition': '-34.9188916666667 138.596861111111',
@@ -1307,7 +1318,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: - `setvalue(tag, value)`: write to the EXIF data in the photo file. To delete a tag, use setvalue with value = `None`. For example:
```python ```python
@@ -1318,7 +1329,7 @@ photo.exiftool.setvalue("XMP:Title", "Title of photo")
photo.exiftool.addvalues("IPTC:Keywords", "vacation", "beach") 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` #### `score`
Returns a [ScoreInfo](#scoreinfo) data class object which provides access to the computed aesthetic scores for each photo. Returns a [ScoreInfo](#scoreinfo) data class object which provides access to the computed aesthetic scores for each photo.
@@ -1326,7 +1337,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. **Note**: Valid only for Photos 5; returns None for earlier Photos versions.
#### `json()` #### `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()`
`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)` `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 +1688,9 @@ Returns a list of [FaceInfo](#faceinfo) objects associated with this person sort
#### `json()` #### `json()`
Returns a json string representation of the PersonInfo instance. Returns a json string representation of the PersonInfo instance.
#### `asdict()`
Returns a dictionary representation of the PersonInfo instance.
### FaceInfo ### 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. [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.
@@ -1871,6 +1888,7 @@ The following substitutions are availabe for use with `PhotoInfo.render_template
|{person}|Person(s) / face(s) in a photo| |{person}|Person(s) / face(s) in a photo|
|{label}|Image categorization label associated with a photo (Photos 5 only)| |{label}|Image categorization label associated with a photo (Photos 5 only)|
|{label_normalized}|All lower case version of 'label' (Photos 5 only)| |{label_normalized}|All lower case version of 'label' (Photos 5 only)|
|{comment}|Comment(s) on shared Photos; format is 'Person name: comment text' (Photos 5 only)|
### Utility Functions ### Utility Functions

View File

@@ -489,6 +489,10 @@ def query_options(f):
help="Search by end item date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601).", help="Search by end item date, e.g. 2000-01-12T12:00:00, 2001-01-12T12:00:00-07:00, or 2000-12-31 (ISO 8601).",
type=DateTimeISO8601(), type=DateTimeISO8601(),
), ),
o("--has-comment", is_flag=True, help="Search for photos that have comments."),
o("--no-comment", is_flag=True, help="Search for photos with no comments."),
o("--has-likes", is_flag=True, help="Search for photos that have likes."),
o("--no-likes", is_flag=True, help="Search for photos with no likes."),
] ]
for o in options[::-1]: for o in options[::-1]:
f = o(f) f = o(f)
@@ -982,6 +986,10 @@ def query(
label, label,
deleted, deleted,
deleted_only, deleted_only,
has_comment,
no_comment,
has_likes,
no_likes,
): ):
""" Query the Photos database using 1 or more search options; """ Query the Photos database using 1 or more search options;
if more than one option is provided, they are treated as "AND" if more than one option is provided, they are treated as "AND"
@@ -1026,6 +1034,8 @@ def query(
(any(place), no_place), (any(place), no_place),
(deleted, deleted_only), (deleted, deleted_only),
(shared, not_shared), (shared, not_shared),
(has_comment, no_comment),
(has_likes, no_likes),
] ]
# print help if no non-exclusive term or a double exclusive term is given # print help if no non-exclusive term or a double exclusive term is given
if any(all(bb) for bb in exclusive) or not any( if any(all(bb) for bb in exclusive) or not any(
@@ -1112,6 +1122,10 @@ def query(
label=label, label=label,
deleted=deleted, deleted=deleted,
deleted_only=deleted_only, deleted_only=deleted_only,
has_comment=has_comment,
no_comment=no_comment,
has_likes=has_likes,
no_likes=no_likes,
) )
# below needed for to make CliRunner work for testing # below needed for to make CliRunner work for testing
@@ -1395,6 +1409,10 @@ def export(
edited_suffix, edited_suffix,
place, place,
no_place, no_place,
has_comment,
no_comment,
has_likes,
no_likes,
no_extended_attributes, no_extended_attributes,
label, label,
deleted, deleted,
@@ -1442,6 +1460,8 @@ def export(
(skip_edited, skip_original_if_edited), (skip_edited, skip_original_if_edited),
(export_as_hardlink, convert_to_jpeg), (export_as_hardlink, convert_to_jpeg),
(shared, not_shared), (shared, not_shared),
(has_comment, no_comment),
(has_likes, no_likes),
] ]
if any(all(bb) for bb in exclusive): if any(all(bb) for bb in exclusive):
click.echo("Incompatible export options", err=True) click.echo("Incompatible export options", err=True)
@@ -1580,6 +1600,10 @@ def export(
label=label, label=label,
deleted=deleted, deleted=deleted,
deleted_only=deleted_only, deleted_only=deleted_only,
has_comment=has_comment,
no_comment=no_comment,
has_likes=has_likes,
no_likes=no_likes,
) )
if photos: if photos:
@@ -1899,6 +1923,10 @@ def _query(
label=None, label=None,
deleted=False, deleted=False,
deleted_only=False, deleted_only=False,
has_comment=False,
no_comment=False,
has_likes=False,
no_likes=False,
): ):
""" run a query against PhotosDB to extract the photos based on user supply criteria """ run a query against PhotosDB to extract the photos based on user supply criteria
used by query and export commands used by query and export commands
@@ -2118,6 +2146,16 @@ def _query(
if has_raw: if has_raw:
photos = [p for p in photos if p.has_raw] photos = [p for p in photos if p.has_raw]
if has_comment:
photos = [p for p in photos if p.comments]
elif no_comment:
photos = [p for p in photos if not p.comments]
if has_likes:
photos = [p for p in photos if p.likes]
elif no_likes:
photos = [p for p in photos if not p.likes]
return photos return photos

View File

@@ -1,4 +1,4 @@
""" version info """ """ version info """
__version__ = "0.36.0" __version__ = "0.36.2"

View File

@@ -228,7 +228,7 @@ class ExifTool:
ver = self.run_commands("-ver", no_file=True) ver = self.run_commands("-ver", no_file=True)
return ver.decode("utf-8") return ver.decode("utf-8")
def as_dict(self): def asdict(self):
""" return dictionary of all EXIF tags and values from exiftool """ return dictionary of all EXIF tags and values from exiftool
returns empty dict if no tags returns empty dict if no tags
""" """
@@ -245,7 +245,7 @@ class ExifTool:
def _read_exif(self): def _read_exif(self):
""" read exif data from file """ """ read exif data from file """
data = self.as_dict() data = self.asdict()
self.data = {k: v for k, v in data.items()} self.data = {k: v for k, v in data.items()}
def __str__(self): def __str__(self):

View File

@@ -66,10 +66,10 @@ class PersonInfo:
# no faces # no faces
return [] return []
def json(self): def asdict(self):
""" Returns JSON representation of class instance """ """ Returns dictionary representation of class instance """
keyphoto = self.keyphoto.uuid if self.keyphoto is not None else None keyphoto = self.keyphoto.uuid if self.keyphoto is not None else None
person = { return {
"uuid": self.uuid, "uuid": self.uuid,
"name": self.name, "name": self.name,
"displayname": self.display_name, "displayname": self.display_name,
@@ -77,7 +77,10 @@ class PersonInfo:
"facecount": self.facecount, "facecount": self.facecount,
"keyphoto": keyphoto, "keyphoto": keyphoto,
} }
return json.dumps(person)
def json(self):
""" Returns JSON representation of class instance """
return json.dumps(self.asdict())
def __str__(self): def __str__(self):
return f"PersonInfo(name={self.name}, display_name={self.display_name}, uuid={self.uuid}, facecount={self.facecount})" return f"PersonInfo(name={self.name}, display_name={self.display_name}, uuid={self.uuid}, facecount={self.facecount})"

View File

@@ -5,6 +5,7 @@ PhotosDB.photos() returns a list of PhotoInfo objects
""" """
import dataclasses import dataclasses
import datetime
import json import json
import logging import logging
import os import os
@@ -950,22 +951,23 @@ class PhotoInfo:
} }
return yaml.dump(info, sort_keys=False) return yaml.dump(info, sort_keys=False)
def json(self): def asdict(self):
""" return JSON representation """ """ 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} folders = {album.title: album.folder_names for album in self.album_info}
exif = dataclasses.asdict(self.exif_info) if self.exif_info else {} 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 {} 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, "uuid": self.uuid,
"filename": self.filename, "filename": self.filename,
"original_filename": self.original_filename, "original_filename": self.original_filename,
"date": self.date.isoformat(), "date": self.date,
"description": self.description, "description": self.description,
"title": self.title, "title": self.title,
"keywords": self.keywords, "keywords": self.keywords,
@@ -974,6 +976,7 @@ class PhotoInfo:
"albums": self.albums, "albums": self.albums,
"folders": folders, "folders": folders,
"persons": self.persons, "persons": self.persons,
"faces": faces,
"path": self.path, "path": self.path,
"ismissing": self.ismissing, "ismissing": self.ismissing,
"hasadjustments": self.hasadjustments, "hasadjustments": self.hasadjustments,
@@ -987,12 +990,13 @@ class PhotoInfo:
"isphoto": self.isphoto, "isphoto": self.isphoto,
"ismovie": self.ismovie, "ismovie": self.ismovie,
"uti": self.uti, "uti": self.uti,
"uti_original": self.uti_original,
"burst": self.burst, "burst": self.burst,
"live_photo": self.live_photo, "live_photo": self.live_photo,
"path_live_photo": self.path_live_photo, "path_live_photo": self.path_live_photo,
"iscloudasset": self.iscloudasset, "iscloudasset": self.iscloudasset,
"incloud": self.incloud, "incloud": self.incloud,
"date_modified": date_modified_iso, "date_modified": self.date_modified,
"portrait": self.portrait, "portrait": self.portrait,
"screenshot": self.screenshot, "screenshot": self.screenshot,
"slow_mo": self.slow_mo, "slow_mo": self.slow_mo,
@@ -1001,6 +1005,8 @@ class PhotoInfo:
"selfie": self.selfie, "selfie": self.selfie,
"panorama": self.panorama, "panorama": self.panorama,
"has_raw": self.has_raw, "has_raw": self.has_raw,
"israw": self.israw,
"raw_original": self.raw_original,
"uti_raw": self.uti_raw, "uti_raw": self.uti_raw,
"path_raw": self.path_raw, "path_raw": self.path_raw,
"place": place, "place": place,
@@ -1014,8 +1020,17 @@ class PhotoInfo:
"original_width": self.original_width, "original_width": self.original_width,
"original_orientation": self.original_orientation, "original_orientation": self.original_orientation,
"original_filesize": self.original_filesize, "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): def __eq__(self, other):
""" Compare two PhotoInfo objects for equality """ """ Compare two PhotoInfo objects for equality """

View File

@@ -1,6 +1,7 @@
""" PhotosDB method for processing comments and likes on shared photos. """ PhotosDB method for processing comments and likes on shared photos.
Do not import this module directly """ Do not import this module directly """
import dataclasses
import datetime import datetime
from dataclasses import dataclass from dataclasses import dataclass
@@ -30,6 +31,9 @@ class CommentInfo:
ismine: bool ismine: bool
text: str text: str
def asdict(self):
return dataclasses.asdict(self)
@dataclass @dataclass
class LikeInfo: class LikeInfo:
@@ -39,6 +43,9 @@ class LikeInfo:
user: str user: str
ismine: bool ismine: bool
def asdict(self):
return dataclasses.asdict(self)
# The following methods do not get imported into PhotosDB # The following methods do not get imported into PhotosDB
# but will get called by _process_comments # but will get called by _process_comments

View File

@@ -1809,7 +1809,8 @@ class PhotosDB:
ZADDITIONALASSETATTRIBUTES.ZORIGINALWIDTH, ZADDITIONALASSETATTRIBUTES.ZORIGINALWIDTH,
ZADDITIONALASSETATTRIBUTES.ZORIGINALORIENTATION, ZADDITIONALASSETATTRIBUTES.ZORIGINALORIENTATION,
ZADDITIONALASSETATTRIBUTES.ZORIGINALFILESIZE, ZADDITIONALASSETATTRIBUTES.ZORIGINALFILESIZE,
{depth_state} {depth_state},
{asset_table}.ZADJUSTMENTTIMESTAMP
FROM {asset_table} FROM {asset_table}
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
ORDER BY {asset_table}.ZUUID """ ORDER BY {asset_table}.ZUUID """
@@ -1853,6 +1854,7 @@ class PhotosDB:
# 34 ZADDITIONALASSETATTRIBUTES.ZORIGINALORIENTATION, # 34 ZADDITIONALASSETATTRIBUTES.ZORIGINALORIENTATION,
# 35 ZADDITIONALASSETATTRIBUTES.ZORIGINALFILESIZE # 35 ZADDITIONALASSETATTRIBUTES.ZORIGINALFILESIZE
# 36 ZGENERICASSET.ZDEPTHSTATES / ZASSET.ZDEPTHTYPE # 36 ZGENERICASSET.ZDEPTHSTATES / ZASSET.ZDEPTHTYPE
# 37 ZGENERICASSET.ZADJUSTMENTTIMESTAMP -- when was photo edited?
for row in c: for row in c:
uuid = row[0] uuid = row[0]
@@ -1866,9 +1868,9 @@ class PhotosDB:
# There are sometimes negative values for lastmodifieddate in the database # There are sometimes negative values for lastmodifieddate in the database
# I don't know what these mean but they will raise exception in datetime if # I don't know what these mean but they will raise exception in datetime if
# not accounted for # not accounted for
info["lastmodifieddate_timestamp"] = row[4] info["lastmodifieddate_timestamp"] = row[37]
try: try:
info["lastmodifieddate"] = datetime.fromtimestamp(row[4] + TIME_DELTA) info["lastmodifieddate"] = datetime.fromtimestamp(row[37] + TIME_DELTA)
except ValueError: except ValueError:
info["lastmodifieddate"] = None info["lastmodifieddate"] = None
except TypeError: except TypeError:

View File

@@ -30,33 +30,34 @@ TEMPLATE_SUBSTITUTIONS = {
"{title}": "Title of the photo", "{title}": "Title of the photo",
"{descr}": "Description of the photo", "{descr}": "Description of the photo",
"{created.date}": "Photo's creation date in ISO format, e.g. '2020-03-22'", "{created.date}": "Photo's creation date in ISO format, e.g. '2020-03-22'",
"{created.year}": "4-digit year of file creation time", "{created.year}": "4-digit year of photo creation time",
"{created.yy}": "2-digit year of file creation time", "{created.yy}": "2-digit year of photo creation time",
"{created.mm}": "2-digit month of the file creation time (zero padded)", "{created.mm}": "2-digit month of the photo creation time (zero padded)",
"{created.month}": "Month name in user's locale of the file creation time", "{created.month}": "Month name in user's locale of the photo creation time",
"{created.mon}": "Month abbreviation in the user's locale of the file creation time", "{created.mon}": "Month abbreviation in the user's locale of the photo creation time",
"{created.dd}": "2-digit day of the month (zero padded) of file creation time", "{created.dd}": "2-digit day of the month (zero padded) of photo creation time",
"{created.dow}": "Day of week in user's locale of the file creation time", "{created.dow}": "Day of week in user's locale of the photo creation time",
"{created.doy}": "3-digit day of year (e.g Julian day) of file creation time, starting from 1 (zero padded)", "{created.doy}": "3-digit day of year (e.g Julian day) of photo creation time, starting from 1 (zero padded)",
"{created.hour}": "2-digit hour of the file creation time", "{created.hour}": "2-digit hour of the photo creation time",
"{created.min}": "2-digit minute of the file creation time", "{created.min}": "2-digit minute of the photo creation time",
"{created.sec}": "2-digit second of the file creation time", "{created.sec}": "2-digit second of the photo creation time",
"{created.strftime}": "Apply strftime template to file creation date/time. Should be used in form " "{created.strftime}": "Apply strftime template to file creation date/time. Should be used in form "
+ "{created.strftime,TEMPLATE} where TEMPLATE is a valid strftime template, e.g. " + "{created.strftime,TEMPLATE} where TEMPLATE is a valid strftime template, e.g. "
+ "{created.strftime,%Y-%U} would result in year-week number of year: '2020-23'. " + "{created.strftime,%Y-%U} would result in year-week number of year: '2020-23'. "
+ "If used with no template will return null value. " + "If used with no template will return null value. "
+ "See https://strftime.org/ for help on strftime templates.", + "See https://strftime.org/ for help on strftime templates.",
"{modified.date}": "Photo's modification date in ISO format, e.g. '2020-03-22'", "{modified.date}": "Photo's modification date in ISO format, e.g. '2020-03-22'",
"{modified.year}": "4-digit year of file modification time", "{modified.year}": "4-digit year of photo modification time",
"{modified.yy}": "2-digit year of file modification time", "{modified.yy}": "2-digit year of photo modification time",
"{modified.mm}": "2-digit month of the file modification time (zero padded)", "{modified.mm}": "2-digit month of the photo modification time (zero padded)",
"{modified.month}": "Month name in user's locale of the file modification time", "{modified.month}": "Month name in user's locale of the photo modification time",
"{modified.mon}": "Month abbreviation in the user's locale of the file modification time", "{modified.mon}": "Month abbreviation in the user's locale of the photo modification time",
"{modified.dd}": "2-digit day of the month (zero padded) of the file modification time", "{modified.dd}": "2-digit day of the month (zero padded) of the photo modification time",
"{modified.doy}": "3-digit day of year (e.g Julian day) of file modification time, starting from 1 (zero padded)", "{modified.dow}": "Day of week in user's locale of the photo modification time",
"{modified.hour}": "2-digit hour of the file modification time", "{modified.doy}": "3-digit day of year (e.g Julian day) of photo modification time, starting from 1 (zero padded)",
"{modified.min}": "2-digit minute of the file modification time", "{modified.hour}": "2-digit hour of the photo modification time",
"{modified.sec}": "2-digit second of the file modification time", "{modified.min}": "2-digit minute of the photo modification time",
"{modified.sec}": "2-digit second of the photo modification time",
# "{modified.strftime}": "Apply strftime template to file modification date/time. Should be used in form " # "{modified.strftime}": "Apply strftime template to file modification date/time. Should be used in form "
# + "{modified.strftime,TEMPLATE} where TEMPLATE is a valid strftime template, e.g. " # + "{modified.strftime,TEMPLATE} where TEMPLATE is a valid strftime template, e.g. "
# + "{modified.strftime,%Y-%U} would result in year-week number of year: '2020-23'. " # + "{modified.strftime,%Y-%U} would result in year-week number of year: '2020-23'. "
@@ -102,7 +103,7 @@ TEMPLATE_SUBSTITUTIONS_MULTI_VALUED = {
"{person}": "Person(s) / face(s) in a photo", "{person}": "Person(s) / face(s) in a photo",
"{label}": "Image categorization label associated with a photo (Photos 5 only)", "{label}": "Image categorization label associated with a photo (Photos 5 only)",
"{label_normalized}": "All lower case version of 'label' (Photos 5 only)", "{label_normalized}": "All lower case version of 'label' (Photos 5 only)",
"{comment}": "Comment(s) on shared Photos; format is 'Person name: comment text' (Photos 5 only)" "{comment}": "Comment(s) on shared Photos; format is 'Person name: comment text' (Photos 5 only)",
} }
# Just the multi-valued substitution names without the braces # Just the multi-valued substitution names without the braces
@@ -445,6 +446,12 @@ class PhotoTemplate:
if self.photo.date_modified if self.photo.date_modified
else None else None
) )
elif field == "modified.dow":
value = (
DateTimeFormatter(self.photo.date_modified).dow
if self.photo.date_modified
else None
)
elif field == "modified.doy": elif field == "modified.doy":
value = ( value = (
DateTimeFormatter(self.photo.date_modified).doy DateTimeFormatter(self.photo.date_modified).doy
@@ -639,7 +646,9 @@ class PhotoTemplate:
else: else:
values.append(album.title) values.append(album.title)
elif field == "comment": elif field == "comment":
values = [f"{comment.user}: {comment.text}" for comment in self.photo.comments] values = [
f"{comment.user}: {comment.text}" for comment in self.photo.comments
]
else: else:
raise ValueError(f"Unhandled template value: {field}") raise ValueError(f"Unhandled template value: {field}")

View File

@@ -491,7 +491,7 @@ class PlaceInfo4(PlaceInfo):
} }
return "PlaceInfo(" + ", ".join([f"{k}='{v}'" for k, v in info.items()]) + ")" return "PlaceInfo(" + ", ".join([f"{k}='{v}'" for k, v in info.items()]) + ")"
def as_dict(self): def asdict(self):
return { return {
"name": self.name, "name": self.name,
"names": self.names._asdict(), "names": self.names._asdict(),
@@ -634,7 +634,7 @@ class PlaceInfo5(PlaceInfo):
} }
return "PlaceInfo(" + ", ".join([f"{k}='{v}'" for k, v in info.items()]) + ")" return "PlaceInfo(" + ", ".join([f"{k}='{v}'" for k, v in info.items()]) + ")"
def as_dict(self): def asdict(self):
return { return {
"name": self.name, "name": self.name,
"names": self.names._asdict(), "names": self.names._asdict(),

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,16 @@
<?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>hostname</key>
<string>Rhets-MacBook-Pro.local</string>
<key>hostuuid</key>
<string>9575E48B-8D5F-5654-ABAC-4431B1167324</string>
<key>pid</key>
<integer>1797</integer>
<key>processname</key>
<string>photolibraryd</string>
<key>uid</key>
<integer>501</integer>
</dict>
</plist>

Binary file not shown.

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: 574 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

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>MigrationService</key>
<dict>
<key>State</key>
<integer>4</integer>
</dict>
<key>MigrationService.LastCompletedTask</key>
<integer>12</integer>
<key>MigrationService.ValidationCounts</key>
<dict>
<key>MigrationDetectedFaceprint</key>
<integer>6</integer>
<key>MigrationManagedAsset</key>
<integer>0</integer>
<key>MigrationSceneClassification</key>
<integer>44</integer>
<key>MigrationUnmanagedAdjustment</key>
<integer>0</integer>
<key>RDVersion.cloudLocalState.CPLIsNotPushed</key>
<integer>7</integer>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,53 @@
<?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/>
<key>ExpandedSidebarItemIdentifiers</key>
<array>
<string>92D68107-B6C7-453B-96D2-97B0F26D5B8B/L0/020</string>
<string>88A5F8B8-5B9A-43C7-BB85-3952B81580EB/L0/020</string>
<string>29EF7A97-7E76-4D5F-A5E0-CC0A93E8524C/L0/020</string>
<string>2C2AF115-BD1D-4434-A747-D1C8BD8E2045/L0/020</string>
<string>CB051A4C-2CB7-4B90-B59B-08CC4D0C2823/L0/020</string>
</array>
<key>Photos</key>
<dict>
<key>CollapsedSidebarSectionIdentifiers</key>
<array/>
<key>ExpandedSidebarItemIdentifiers</key>
<array>
<string>TopLevelAlbums</string>
<string>TopLevelSlideshows</string>
</array>
<key>IPXWorkspaceControllerZoomLevelsKey</key>
<dict>
<key>kZoomLevelIdentifierAlbums</key>
<integer>7</integer>
<key>kZoomLevelIdentifierVersions</key>
<integer>7</integer>
</dict>
<key>lastAddToDestination</key>
<dict>
<key>key</key>
<integer>1</integer>
<key>lastKnownDisplayName</key>
<string>September 28, 2018</string>
<key>type</key>
<string>album</string>
<key>uuid</key>
<string>DFFKmHt3Tk+AGzZLe2Xq+g</string>
</dict>
<key>lastKnownItemCounts</key>
<dict>
<key>other</key>
<integer>0</integer>
<key>photos</key>
<integer>7</integer>
<key>videos</key>
<integer>0</integer>
</dict>
</dict>
</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-10-17T23:45:25Z</date>
<key>BackgroundHighlightEnrichment</key>
<date>2020-10-17T23:45:25Z</date>
<key>BackgroundJobAssetRevGeocode</key>
<date>2020-10-17T23:45:25Z</date>
<key>BackgroundJobSearch</key>
<date>2020-10-17T23:45:25Z</date>
<key>BackgroundPeopleSuggestion</key>
<date>2020-10-17T23:45:25Z</date>
<key>BackgroundUserBehaviorProcessor</key>
<date>2020-10-17T23:45:25Z</date>
<key>PhotoAnalysisGraphLastBackgroundGraphConsistencyUpdateJobDateKey</key>
<date>2020-10-17T23:45:33Z</date>
<key>PhotoAnalysisGraphLastBackgroundGraphRebuildJobDate</key>
<date>2020-10-17T23:45:24Z</date>
<key>PhotoAnalysisGraphLastBackgroundMemoryGenerationJobDate</key>
<date>2020-10-17T23:45:26Z</date>
<key>SiriPortraitDonation</key>
<date>2020-10-17T23:45:25Z</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>

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>NumberOfFacesProcessedOnLastRun</key>
<integer>11</integer>
<key>ProcessedInQuiescentState</key>
<true/>
<key>SuggestedMeIdentifier</key>
<string></string>
<key>Version</key>
<integer>4</integer>
</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>FaceIDModelLastGenerationKey</key>
<date>2020-10-17T23:45:32Z</date>
<key>LastContactClassificationKey</key>
<date>2020-10-17T23:45:54Z</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>PVClustererBringUpState</key>
<integer>50</integer>
</dict>
</plist>

View File

@@ -0,0 +1,12 @@
<?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>IncrementalPersonProcessingStage</key>
<integer>6</integer>
<key>PersonBuilderLastMinimumFaceGroupSizeForCreatingMergeCandidates</key>
<integer>15</integer>
<key>PersonBuilderMergeCandidatesEnabled</key>
<true/>
</dict>
</plist>

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