Compare commits

..

82 Commits

Author SHA1 Message Date
Rhet Turnbull
e39776b51e Added --field to dump and query, #777 (#779) 2022-08-27 21:05:57 -07:00
Rhet Turnbull
02d772c921 Updated CHANGELOG.md [skip ci] 2022-08-27 13:48:39 -07:00
allcontributors[bot]
817bdf0604 docs: add tkrunning as a contributor for code, bug (#776)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2022-08-27 13:47:48 -07:00
allcontributors[bot]
a74520f747 docs: add jmuccigr as a contributor for bug, ideas (#775)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2022-08-27 13:46:13 -07:00
Rhet Turnbull
bb480f6991 Release 0.51.4, added --print to dump 2022-08-27 11:25:46 -07:00
Rhet Turnbull
af9311c9c8 Fixed --print to work with {tab} 2022-08-27 11:11:38 -07:00
Rhet Turnbull
b12d112793 Updated CHANGELOG.md [skip ci] 2022-08-27 10:53:42 -07:00
Rhet Turnbull
5eaeb72c3e Added --print to dump, added {tab} 2022-08-27 10:50:25 -07:00
Rhet Turnbull
320fb86559 Added bump2version 2022-08-27 09:47:52 -07:00
Rhet Turnbull
d576ca5494 Release 0.51.3, added --print (#769), --quiet (#770) 2022-08-27 09:31:37 -07:00
Rhet Turnbull
8e986b451e Added --print, --quiet, #769, #770 (#773) 2022-08-26 22:17:12 -07:00
Rhet Turnbull
f0bdfb5eac Updated CHANGELOG.md [skip ci] 2022-08-26 07:03:59 -07:00
Rhet Turnbull
4ca681ee4f Release 0.51.2, added new filter(x) to template filters (#772) 2022-08-26 06:55:45 -07:00
Rhet Turnbull
66f6002a57 Feature filter filter 759 (#771)
* Added filter(x) filter, #759

* Added int, float filters
2022-08-26 06:39:32 -07:00
Rhet Turnbull
d845e9b66e Release 0.51.1, added --report to import (#767) 2022-08-22 09:31:47 -07:00
Rhet Turnbull
991511af07 Added --report to import command (#766) 2022-08-22 07:29:53 -07:00
Rhet Turnbull
3c98906158 Fixed template function to work with import command (#765) 2022-08-21 14:33:18 -07:00
Rhet Turnbull
08b806ff7d Updated CHANGELOG.md [skip ci] 2022-08-21 12:39:28 -07:00
Rhet Turnbull
b5f4c48ec9 Updated README [skip ci] 2022-08-21 12:37:56 -07:00
Rhet Turnbull
b7cfd1da9b Release 0.51.0 (#763) 2022-08-21 09:45:32 -07:00
Rhet Turnbull
c88fc75013 Feature add import 754 (#762)
* Initial alpha version of import command

* Refactored

* Improved help, added --clear-metadata

* Added --clear-metadata, --exiftool to import

* Added --keyword, --title, --description

* Added --location

* Added test for --location

* Changed --auto-folder to --split-folder, added docs

* Added --walk, updated docs

* Added --check-templates

* Updated help text for import
2022-08-21 09:07:22 -07:00
Rhet Turnbull
46738d05b2 Updated xmp_rating example 2022-08-15 10:13:25 -07:00
Rhet Turnbull
1e053aa708 Updated xmp_rating example 2022-08-15 10:08:00 -07:00
Rhet Turnbull
c7e3a552db Updated examples [skip ci] 2022-08-14 14:39:51 -07:00
Rhet Turnbull
5979d6245d Updated tested versions (#757) 2022-08-13 11:44:15 -07:00
Rhet Turnbull
099afdb3ad Updated CHANGELOG.md [skip ci] 2022-08-13 08:59:26 -07:00
Rhet Turnbull
8e3578e29b Release 0.50.13 (#756) 2022-08-13 08:50:16 -07:00
Rhet Turnbull
38a5998063 Feature orphans (#755)
* Initial implementation of orphan command

* Implemented orphans command, #84
2022-08-13 08:43:02 -07:00
Rhet Turnbull
2103d8bcad Added bad_photos example [skip ci] 2022-08-11 22:06:33 -07:00
Rhet Turnbull
26a9028497 Add PhotosAlbumPhotosKit to __all__ 2022-08-10 21:37:33 -06:00
Rhet Turnbull
e41f89480a Add PhotosAlbum to osxphotos __all__ 2022-08-10 20:53:07 -06:00
Rhet Turnbull
db3c37fb5b Hot fix for 749 (#750) 2022-08-08 06:26:08 -07:00
Rhet Turnbull
3d83e184b8 Added strip_live.py example (#747) 2022-08-06 21:19:51 -07:00
Rhet Turnbull
28e03ee86a Added reddit badge for r/osxphotos (#746) 2022-08-02 07:11:55 -07:00
Rhet Turnbull
fac45c7141 Updated CHANGELOG.md [skip ci] 2022-08-01 21:00:44 -07:00
Rhet Turnbull
f4154e8691 Feature not reference 738 (#744)
* Added --not-reference, 738

* Release files for #738
2022-07-28 06:24:58 -07:00
allcontributors[bot]
b4c4d9fb90 docs: add nullpointerninja as a contributor for ideas (#743)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2022-07-27 09:46:19 -07:00
allcontributors[bot]
402bbbdbae docs: add franzone as a contributor for bug (#742)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2022-07-27 09:44:25 -07:00
Rhet Turnbull
f7771909d6 Updated docs for v0.58.10 [skip-ci] (#741) 2022-07-27 08:25:06 -07:00
Rhet Turnbull
76625b9e84 Feature add keep 730 (#740)
* Implemented #730, --keep

* Implemented #739, fixed --keep to accept relative paths

* Updated to macos-latest

Ref: https://github.com/actions/virtual-environments/issues/5583

* Release files for #730
2022-07-27 08:14:00 -07:00
allcontributors[bot]
fb4329e0ed docs: add Se7enair as a contributor for ideas (#737)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2022-07-24 04:50:20 -07:00
Rhet Turnbull
2b52460de7 Updated CHANGELOG.md [skip ci] 2022-07-23 18:27:16 -07:00
Rhet Turnbull
f8f9bd7b93 Release files for #732, add --favorite-rating 2022-07-23 18:25:07 -07:00
Rhet Turnbull
5d33dcdcc3 Implemented --favorite-rating, #732 2022-07-23 18:16:06 -07:00
Rhet Turnbull
b4caea15fa Updated CHANGELOG.md [skip ci] 2022-07-23 16:44:50 -07:00
Rhet Turnbull
6e33540e60 Fixed report_summart view 2022-07-23 16:42:02 -07:00
Rhet Turnbull
2e85f9be89 Fixed report_summart view 2022-07-23 16:33:24 -07:00
Rhet Turnbull
7484c7b994 Added report_summary view to export report database 2022-07-23 16:27:49 -07:00
Rhet Turnbull
f279217118 Added report_summary view to export report database 2022-07-23 16:21:21 -07:00
Rhet Turnbull
b998684821 Updated CHANGELOG.md [skip ci] 2022-07-23 15:50:03 -07:00
Rhet Turnbull
f3557d1991 Updated docs [skip ci] 2022-07-23 15:42:34 -07:00
Rhet Turnbull
855d417e81 Refactored implementation for #731 2022-07-23 15:33:31 -07:00
Rhet Turnbull
bd33b61882 Implemented #731, export_id in report database 2022-07-23 10:11:39 -07:00
allcontributors[bot]
337d422346 docs: add infused-kim as a contributor for ideas (#736)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2022-07-23 07:30:47 -07:00
Rhet Turnbull
7497a02aaf Added live video and raw photo size to inspect, #734 2022-07-23 07:26:39 -07:00
Rhet Turnbull
1fadea8864 Updated CHANGELOG.md [skip ci] 2022-07-15 22:56:10 -04:00
Rhet Turnbull
5a43fb7410 Bug fix for #726 2022-07-15 22:53:09 -04:00
Rhet Turnbull
30bf06e794 Possible fix for #726 2022-07-15 22:38:47 -04:00
Rhet Turnbull
091adc9925 Updated CHANGELOG.md [skip ci] 2022-07-01 08:36:51 -07:00
allcontributors[bot]
f000f21ba5 docs: add nullpointerninja as a contributor for bug (#724)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2022-07-01 08:35:02 -07:00
Rhet Turnbull
8ac584e708 Fix for large files and exiftool, #722 2022-07-01 08:14:56 -07:00
Rhet Turnbull
292fdf3c74 Fix for large files and exiftool, #722 (#723) 2022-07-01 08:03:21 -07:00
Rhet Turnbull
c20a3994c0 Added example [skip ci] 2022-06-20 00:00:55 -07:00
Rhet Turnbull
7d84b3d6cc Updated README.md [skip ci] 2022-06-18 11:50:13 -07:00
Rhet Turnbull
6b0db223a7 Updated README.md [skip ci] 2022-06-18 11:48:15 -07:00
Rhet Turnbull
451e01e65e Updated CHANGELOG.md [skip ci] 2022-06-17 15:08:07 -07:00
Rhet Turnbull
51317a607c Added initial support for macOS Ventura/13.0 beta 2022-06-17 12:26:22 -07:00
Rhet Turnbull
5f63cccc7c Initial support for Ventura developer preview (#715) 2022-06-17 12:11:17 -07:00
Rhet Turnbull
b3a935bd90 Added missing import 2022-06-15 08:26:01 -07:00
Rhet Turnbull
56435b101f Updated examples [skip ci] 2022-06-13 06:02:04 -07:00
Rhet Turnbull
04c2f6121a Updated examples [skip ci] 2022-06-12 21:56:46 -07:00
Rhet Turnbull
f47aa72165 Updated examples [skip ci] 2022-06-12 21:50:51 -07:00
Rhet Turnbull
561c6846e4 Added example [skip ci] 2022-06-12 21:33:53 -07:00
Rhet Turnbull
0d7e324f02 Added metadata_changed to ExportResults docs 2022-06-03 09:36:29 -07:00
Rhet Turnbull
c2f02c3b7b Added --template to inspect command 2022-05-29 09:39:40 -07:00
Rhet Turnbull
04e1149cad Fixed docs 2022-05-29 08:18:18 -07:00
Rhet Turnbull
bb7a81f9ed Updated CHANGELOG.md [skip ci] 2022-05-28 23:20:11 -07:00
Rhet Turnbull
203dccb39f Fixed shortuuid docs 2022-05-28 23:14:05 -07:00
Rhet Turnbull
75568269bb Added shortuuid, #314 2022-05-28 23:03:04 -07:00
Rhet Turnbull
9e9266ec9c Added slice, sslice filters 2022-05-28 22:07:31 -07:00
Rhet Turnbull
6f1e81b038 Updated join so join() works correctly, #706 2022-05-28 19:12:46 -07:00
Rhet Turnbull
0122f9b2d8 Updated CHANGELOG.md [skip ci] 2022-05-28 19:04:45 -07:00
286 changed files with 9646 additions and 1115 deletions

View File

@@ -326,6 +326,63 @@
"contributions": [
"code"
]
},
{
"login": "nullpointerninja",
"name": "nullpointerninja",
"avatar_url": "https://avatars.githubusercontent.com/u/62975432?v=4",
"profile": "https://github.com/nullpointerninja",
"contributions": [
"bug",
"ideas"
]
},
{
"login": "infused-kim",
"name": "Kim",
"avatar_url": "https://avatars.githubusercontent.com/u/7404004?v=4",
"profile": "https://github.com/infused-kim",
"contributions": [
"ideas"
]
},
{
"login": "Se7enair",
"name": "Christoph",
"avatar_url": "https://avatars.githubusercontent.com/u/1680106?v=4",
"profile": "https://github.com/Se7enair",
"contributions": [
"ideas"
]
},
{
"login": "franzone",
"name": "franzone",
"avatar_url": "https://avatars.githubusercontent.com/u/900684?v=4",
"profile": "http://www.franzone.com",
"contributions": [
"bug"
]
},
{
"login": "jmuccigr",
"name": "John Muccigrosso",
"avatar_url": "https://avatars.githubusercontent.com/u/615115?v=4",
"profile": "http://jmuccigr.github.io/",
"contributions": [
"bug",
"ideas"
]
},
{
"login": "tkrunning",
"name": "Thomas K. Running",
"avatar_url": "https://avatars.githubusercontent.com/u/1646041?v=4",
"profile": "https://nomadgate.com",
"contributions": [
"code",
"bug"
]
}
],
"contributorsPerLine": 7,

8
.bumpversion.cfg Normal file
View File

@@ -0,0 +1,8 @@
[bumpversion]
current_version = 0.51.4
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
serialize = {major}.{minor}.{patch}
[bumpversion:file:osxphotos/_version.py]
parse = __version__\s=\s\"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\"
serialize = {major}.{minor}.{patch}

View File

@@ -9,7 +9,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
os: [macos-10.15]
os: [macos-latest]
python-version: ['3.8', '3.9', '3.10']
steps:

View File

@@ -1626,18 +1626,6 @@ Returns list of x, y coordinates as tuples `[(x0, y0), (x1, y1)]` representing t
Coordinates as (x, y) tuple for the center of the detected face.
#### `mouth`
Coordinates as (x, y) tuple for the mouth of the detected face.
#### `left_eye`
Coordinates as (x, y) tuple for the left eye of the detected face.
#### `right_eye`
Coordinates as (x, y) tuple for the right eye of the detected face.
#### `size_pixels`
Diameter of detected face region in pixels.
@@ -1654,29 +1642,25 @@ Roll of face region in radians.
Pitch of face region in radians.
**Note**: Only valid on Photos version <= 4, otherwise returns 0
#### yaw
Yaw of face region in radians.
**Note**: Only valid on Photos version <= 4, otherwise returns 0
#### `Additional properties`
The following additional properties are also available but are not yet fully documented.
* `center_x`: x coordinate of center of face in Photos' internal reference frame
* `center_y`: y coordinate of center of face in Photos' internal reference frame
* `mouth_x`: x coordinate of mouth in Photos' internal reference frame
* `mouth_y`: y coordinate of mouth in Photos' internal reference frame
* `left_eye_x`: x coordinate of left eye in Photos' internal reference frame
* `left_eye_y`: y coordinate of left eye in Photos' internal reference frame
* `right_eye_x`: x coordinate of right eye in Photos' internal reference frame
* `right_eye_y`: y coordinate of right eye in Photos' internal reference frame
* `size`: size of face region in Photos' internal reference frame
* `quality`: quality measure of detected face
* `source_width`: width in pixels of photo
* `source_height`: height in pixels of photo
* `has_smile`:
* `left_eye_closed`:
* `right_eye_closed`:
* `manual`:
* `face_type`:
* `age_type`:
@@ -1825,10 +1809,15 @@ Valid filters are:
- `rsort`: Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
- `reverse`: Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
- `uniq`: Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].
- `join(x)`: Join list of values with delimiter x, e.g. join(:): ['a', 'b', 'c'] => 'a:b:c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.
- `join(x)`: Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.
- `append(x)`: Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].
- `prepend(x)`: Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].
- `remove(x)`: Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].
- `slice(start:stop:step)`: Slice list using same semantics as Python's list slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().
- `sslice(start:stop:step)`: [s(tring) slice] Slice values in a list using same semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().
- `filter(x)`: Filter list of values using predicate x; for example, `{folder_album|filter(contains Events)}` returns only folders/albums containing the word 'Events' in their path.
- `int`: Convert values in list to integer, e.g. 1.0 => 1. If value cannot be converted to integer, remove value from list. ['1.1', 'x'] => ['1']. See also float.
- `float`: Convert values in list to floating point number, e.g. 1 => 1.0. If value cannot be converted to float, remove value from list. ['1', 'x'] => ['1.0']. See also int.
e.g. if Photo keywords are `["FOO","bar"]`:
@@ -2001,6 +1990,7 @@ cog.out(get_template_field_table())
|{exif.lens_model}|Lens model from original photo's EXIF information as imported by Photos, e.g. 'iPhone 6s back camera 4.15mm f/2.2'|
|{moment}|The moment title of the photo|
|{uuid}|Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'|
|{shortuuid}|A shorter representation of photo's internal universally unique identifier (UUID) for the photo, a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'|
|{id}|A unique number for the photo based on its primary key in the Photos database. A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{id:05d}' which results in 00001, 00002, 00003...etc. |
|{album_seq}|An integer, starting at 0, indicating the photo's index (sequence) in the containing album. Only valid when used in a '--filename' template and only when '{album}' or '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{album_seq}_{original_name}"'. To start counting at a value other than 0, append append '(starting_value)' to the field name. For example, to start counting at 1 instead of 0: '{album_seq(1)}'. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{album_seq:05d}' which results in 00000, 00001, 00002...etc. To format while also using a starting value: '{album_seq:05d(1)}' which results in 0001, 00002...etc.This may result in incorrect sequences if you have duplicate albums with the same name; see also '{folder_album_seq}'.|
|{folder_album_seq}|An integer, starting at 0, indicating the photo's index (sequence) in the containing album and folder path. Only valid when used in a '--filename' template and only when '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{folder_album_seq}_{original_name}"'. To start counting at a value other than 0, append '(starting_value)' to the field name. For example, to start counting at 1 instead of 0: '{folder_album_seq(1)}' May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{folder_album_seq:05d}' which results in 00000, 00001, 00002...etc. To format while also using a starting value: '{folder_album_seq:05d(1)}' which results in 0001, 00002...etc.This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '{album_seq}'. |
@@ -2017,8 +2007,9 @@ cog.out(get_template_field_table())
|{newline}|A newline: '\n'|
|{lf}|A line feed: '\n', alias for {newline}|
|{cr}|A carriage return: '\r'|
|{crlf}|a carriage return + line feed: '\r\n'|
|{osxphotos_version}|The osxphotos version, e.g. '0.50.1'|
|{crlf}|A carriage return + line feed: '\r\n'|
|{tab}|:A tab: '\t'|
|{osxphotos_version}|The osxphotos version, e.g. '0.51.4'|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|{album}|Album(s) photo is contained in|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|
@@ -2063,10 +2054,11 @@ True
['Keyword1', 'Keyword2', 'Keyword3']
```
`ExifTool(filepath, exiftool=None)`
`ExifTool(filepath, exiftool=None, large_file_support=True)`
* `filepath`: str, path to photo
* `exiftool`: str, optional path to `exiftool`; if not provided, will look for `exiftool` in the system path
* `large_file_support`: bool, if True, enables large file support in exiftool (`-api largefilesupport=1`)
#### ExifTool methods
@@ -2188,6 +2180,7 @@ Attributes:
* touched: list of files touched during export (e.g. file date/time updated with touch_file=True)
* to_touch: Reserved for internal use of export
* converted_to_jpeg: list of files converted to jpeg when convert_to_jpeg=True
* metadata_changed: list of filenames that had metadata changes since last export
* sidecar_json_written: list of JSON sidecars written
* sidecar_json_skipped: list of JSON sidecars skipped when update=True
* sidecar_exiftool_written: list of exiftool sidecars written

View File

@@ -4,6 +4,159 @@ 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).
#### [v0.51.4](https://github.com/RhetTbull/osxphotos/compare/v0.51.3...v0.51.4)
> 27 August 2022
- Release 0.51.4, added --print to dump [`bb480f6`](https://github.com/RhetTbull/osxphotos/commit/bb480f69914ed351b6f4309b0f6aa539add1a9fb)
- Added --print to dump, added {tab} [`5eaeb72`](https://github.com/RhetTbull/osxphotos/commit/5eaeb72c3ee296af6abc6ca6ddf8ad05baf02052)
- Fixed --print to work with {tab} [`af9311c`](https://github.com/RhetTbull/osxphotos/commit/af9311c9c86a3d0a5764ebd1539d40f14e62f2ec)
#### [v0.51.3](https://github.com/RhetTbull/osxphotos/compare/v0.51.2...v0.51.3)
> 27 August 2022
- Added --print, --quiet, #769, #770 [`#773`](https://github.com/RhetTbull/osxphotos/pull/773)
- Release 0.51.3, added --print (#769), --quiet (#770) [`d576ca5`](https://github.com/RhetTbull/osxphotos/commit/d576ca54948c0cbbd16f04231459a294f0333f89)
- Added bump2version [`320fb86`](https://github.com/RhetTbull/osxphotos/commit/320fb8655980882fd75e354d33450f0904babf2a)
#### [v0.51.2](https://github.com/RhetTbull/osxphotos/compare/v0.51.1...v0.51.2)
> 26 August 2022
- Release 0.51.2, added new filter(x) to template filters [`#772`](https://github.com/RhetTbull/osxphotos/pull/772)
- Feature filter filter 759 [`#771`](https://github.com/RhetTbull/osxphotos/pull/771)
#### [v0.51.1](https://github.com/RhetTbull/osxphotos/compare/v0.51.0...v0.51.1)
> 22 August 2022
- Release 0.51.1, added --report to import [`#767`](https://github.com/RhetTbull/osxphotos/pull/767)
- Added --report to import command [`#766`](https://github.com/RhetTbull/osxphotos/pull/766)
- Fixed template function to work with import command [`#765`](https://github.com/RhetTbull/osxphotos/pull/765)
- Updated README [skip ci] [`b5f4c48`](https://github.com/RhetTbull/osxphotos/commit/b5f4c48ec98b344b5d7ed7c2a5e9a445d322df13)
#### [v0.51.0](https://github.com/RhetTbull/osxphotos/compare/v0.50.13...v0.51.0)
> 21 August 2022
- Release 0.51.0 [`#763`](https://github.com/RhetTbull/osxphotos/pull/763)
- Feature add import 754 [`#762`](https://github.com/RhetTbull/osxphotos/pull/762)
- Updated tested versions [`#757`](https://github.com/RhetTbull/osxphotos/pull/757)
- Updated examples [skip ci] [`c7e3a55`](https://github.com/RhetTbull/osxphotos/commit/c7e3a552db60321fc9999153b6b5624bc1bb76dc)
- Updated xmp_rating example [`1e053aa`](https://github.com/RhetTbull/osxphotos/commit/1e053aa7086af44a207d1be045d698c7d10b97f5)
- Updated xmp_rating example [`46738d0`](https://github.com/RhetTbull/osxphotos/commit/46738d05b213d1d8ef390add71142623892385ce)
#### [v0.50.13](https://github.com/RhetTbull/osxphotos/compare/v0.50.12...v0.50.13)
> 13 August 2022
- Release 0.50.13 [`#756`](https://github.com/RhetTbull/osxphotos/pull/756)
- Feature orphans [`#755`](https://github.com/RhetTbull/osxphotos/pull/755)
- Added bad_photos example [skip ci] [`2103d8b`](https://github.com/RhetTbull/osxphotos/commit/2103d8bcad65807d23f14315dbc4a76b3b6badfe)
- Add PhotosAlbumPhotosKit to __all__ [`26a9028`](https://github.com/RhetTbull/osxphotos/commit/26a9028497d147c047a4b73c5cf0fe964f7b2e00)
- Add PhotosAlbum to osxphotos __all__ [`e41f894`](https://github.com/RhetTbull/osxphotos/commit/e41f89480a37a19778c7b97fa0c8b0ceedfd56cd)
#### [v0.50.12](https://github.com/RhetTbull/osxphotos/compare/v0.50.11...v0.50.12)
> 8 August 2022
- Hot fix for 749 [`#750`](https://github.com/RhetTbull/osxphotos/pull/750)
- Added strip_live.py example [`#747`](https://github.com/RhetTbull/osxphotos/pull/747)
- Added reddit badge for r/osxphotos [`#746`](https://github.com/RhetTbull/osxphotos/pull/746)
#### [v0.50.11](https://github.com/RhetTbull/osxphotos/compare/v0.50.10...v0.50.11)
> 28 July 2022
- Feature not reference 738 [`#744`](https://github.com/RhetTbull/osxphotos/pull/744)
- docs: add nullpointerninja as a contributor for ideas [`#743`](https://github.com/RhetTbull/osxphotos/pull/743)
- docs: add franzone as a contributor for bug [`#742`](https://github.com/RhetTbull/osxphotos/pull/742)
#### [v0.50.10](https://github.com/RhetTbull/osxphotos/compare/v0.50.9...v0.50.10)
> 27 July 2022
- Updated docs for v0.58.10 [skip-ci] [`#741`](https://github.com/RhetTbull/osxphotos/pull/741)
- Feature add keep 730 [`#740`](https://github.com/RhetTbull/osxphotos/pull/740)
- docs: add Se7enair as a contributor for ideas [`#737`](https://github.com/RhetTbull/osxphotos/pull/737)
#### [v0.50.9](https://github.com/RhetTbull/osxphotos/compare/v0.50.8...v0.50.9)
> 23 July 2022
- Release files for #732, add --favorite-rating [`f8f9bd7`](https://github.com/RhetTbull/osxphotos/commit/f8f9bd7b933c077528649560d692ceb22d254768)
- Implemented --favorite-rating, #732 [`5d33dcd`](https://github.com/RhetTbull/osxphotos/commit/5d33dcdcc3af1ca9dfa11b7be2ab51f6906d9e61)
#### [v0.50.8](https://github.com/RhetTbull/osxphotos/compare/v0.50.7...v0.50.8)
> 23 July 2022
- Added report_summary view to export report database [`7484c7b`](https://github.com/RhetTbull/osxphotos/commit/7484c7b9942d430089039d203fb7dc37004e8af9)
- Fixed report_summart view [`2e85f9b`](https://github.com/RhetTbull/osxphotos/commit/2e85f9be891e1d762b548abbea7b5ca2b3ed7da3)
- Added report_summary view to export report database [`f279217`](https://github.com/RhetTbull/osxphotos/commit/f279217118e2051ecdb54d14fb207c627ab36a7e)
#### [v0.50.7](https://github.com/RhetTbull/osxphotos/compare/v0.50.6...v0.50.7)
> 23 July 2022
- docs: add infused-kim as a contributor for ideas [`#736`](https://github.com/RhetTbull/osxphotos/pull/736)
- Implemented #731, export_id in report database [`bd33b61`](https://github.com/RhetTbull/osxphotos/commit/bd33b61882fa746e9750be7cd80e6e2f785131b2)
- Refactored implementation for #731 [`855d417`](https://github.com/RhetTbull/osxphotos/commit/855d417e816d796165208658dc276ca378bf3337)
- Added live video and raw photo size to inspect, #734 [`7497a02`](https://github.com/RhetTbull/osxphotos/commit/7497a02aaf155bf719e4ccc2c9d3a443f351353a)
- Updated docs [skip ci] [`f3557d1`](https://github.com/RhetTbull/osxphotos/commit/f3557d1991021766dfa8a5018f95b3f7a95777b6)
#### [v0.50.6](https://github.com/RhetTbull/osxphotos/compare/0.50.5...v0.50.6)
> 15 July 2022
- docs: add nullpointerninja as a contributor for bug [`#724`](https://github.com/RhetTbull/osxphotos/pull/724)
- Bug fix for #726 [`5a43fb7`](https://github.com/RhetTbull/osxphotos/commit/5a43fb7410c2fc5407bbe41f4b7c6e3cefb54f0d)
- Possible fix for #726 [`30bf06e`](https://github.com/RhetTbull/osxphotos/commit/30bf06e79489005f85a72fa45a5986cac506fdad)
#### [0.50.5](https://github.com/RhetTbull/osxphotos/compare/v0.50.4...0.50.5)
> 1 July 2022
- Fix for large files and exiftool, #722 [`#723`](https://github.com/RhetTbull/osxphotos/pull/723)
- Added example [skip ci] [`c20a399`](https://github.com/RhetTbull/osxphotos/commit/c20a3994c01bddea2e3bc42a9656ac9e6c858f34)
- Updated README.md [skip ci] [`6b0db22`](https://github.com/RhetTbull/osxphotos/commit/6b0db223a7d43ab2dd0d2358e549f7014bf6dd32)
- Updated README.md [skip ci] [`7d84b3d`](https://github.com/RhetTbull/osxphotos/commit/7d84b3d6cc0305e381f5b4870a2a83640d3d7d2a)
#### [v0.50.4](https://github.com/RhetTbull/osxphotos/compare/v0.50.3...v0.50.4)
> 17 June 2022
- Initial support for Ventura developer preview [`#715`](https://github.com/RhetTbull/osxphotos/pull/715)
- Added initial support for macOS Ventura/13.0 beta [`51317a6`](https://github.com/RhetTbull/osxphotos/commit/51317a607c5e3788f9b6ebbde83027fa2e31cc4f)
- Added example [skip ci] [`561c684`](https://github.com/RhetTbull/osxphotos/commit/561c6846e40b4bbbc58c46a802309d56553238e1)
- Updated examples [skip ci] [`04c2f61`](https://github.com/RhetTbull/osxphotos/commit/04c2f6121affc313f98562959be2aa74de93dbe6)
- Updated examples [skip ci] [`56435b1`](https://github.com/RhetTbull/osxphotos/commit/56435b101fb99893916728206ec74154fbc203b3)
- Updated examples [skip ci] [`f47aa72`](https://github.com/RhetTbull/osxphotos/commit/f47aa721654e4c002bdfd7bef5997011d86564ec)
#### [v0.50.3](https://github.com/RhetTbull/osxphotos/compare/v0.50.2...v0.50.3)
> 29 May 2022
- Added --template to inspect command [`c2f02c3`](https://github.com/RhetTbull/osxphotos/commit/c2f02c3b7bf212c8c987868cd8a2374fcc8ffaf0)
- Fixed docs [`04e1149`](https://github.com/RhetTbull/osxphotos/commit/04e1149cadce034fc36fd6a593975432c6c99d07)
#### [v0.50.2](https://github.com/RhetTbull/osxphotos/compare/v0.50.1...v0.50.2)
> 28 May 2022
- Added shortuuid, #314 [`7556826`](https://github.com/RhetTbull/osxphotos/commit/75568269bbd7b05a22f9fd00acd6f59691f1a507)
- Added slice, sslice filters [`9e9266e`](https://github.com/RhetTbull/osxphotos/commit/9e9266ec9c890ed6fb09d61b1a075be954bef7c1)
- Fixed shortuuid docs [`203dccb`](https://github.com/RhetTbull/osxphotos/commit/203dccb39fbe68b996d53126b52fd3fcedc5f0a1)
#### [v0.50.1](https://github.com/RhetTbull/osxphotos/compare/v0.50.0...v0.50.1)
> 28 May 2022
- Updated README.md, #707 [skip ci] [`a049b99`](https://github.com/RhetTbull/osxphotos/commit/a049b99b0ef67803da917f92ecb24b18fbe6a6c9)
- Version 0.50.1 with --delete-file, --delete-uuid exportdb commands [`6c1650b`](https://github.com/RhetTbull/osxphotos/commit/6c1650b7cffefc223374f66012393f14d443fa72)
- Updated docs [skip ci] [`b6e7a75`](https://github.com/RhetTbull/osxphotos/commit/b6e7a75a8110e7f71ae615e50e91419d92f5b59e)
#### [v0.50.0](https://github.com/RhetTbull/osxphotos/compare/v0.49.9...v0.50.0)
> 28 May 2022

373
README.md
View File

@@ -5,8 +5,9 @@
[![tests](https://github.com/RhetTbull/osxphotos/workflows/Tests/badge.svg)](https://github.com/RhetTbull/osxphotos/workflows/Tests/badge.svg)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/osxphotos)
[![Downloads](https://static.pepy.tech/personalized-badge/osxphotos?period=month&units=international_system&left_color=black&right_color=brightgreen&left_text=downloads/month)](https://pepy.tech/project/osxphotos)
[![subreddit](https://img.shields.io/reddit/subreddit-subscribers/osxphotos?style=social)](https://www.reddit.com/r/osxphotos/)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-34-orange.svg?style=flat)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-40-orange.svg?style=flat)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
OSXPhotos provides the ability to interact with and query Apple's Photos.app library on macOS. You can query the Photos library database — for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc. You can also easily export both the original and edited photos.
@@ -36,13 +37,18 @@ Only works on macOS (aka Mac OS X). Tested on macOS Sierra (10.12.6) through mac
| macOS Version | macOS name | Photos.app version |
| ----------------- |------------|:-------------------|
| 12.0 | Monterey | 7.0 |
| 13.0 | Ventura | 8.0 ? * |
| 12.0 - 12.4 | Monterey | 7.0 ✅ ** |
| 10.16, 11.0-11.4 | Big Sur | 6.0 ✅ |
| 10.15.1 - 10.15.7 | Catalina | 5.0 ✅ |
| 10.14.5, 10.14.6 | Mojave | 4.0 ✅ |
| 10.13.6 | High Sierra| 3.0 ✅ |
| 10.12.6 | Sierra | 2.0 ✅ |
\* Basic functionality has been tested on a Photos library created with the developer preview of macOS Ventura (13.0). I do not have access to a Mac running Ventura beta to do further testing.
\*\* Some features may not be fully supported on Monterey. Notably, `--use-photokit` and `--download-missing` may or may not work depending on your configuration; this is a known issue that will be fixed if I can find a solution. Many users successfully use OSXPhotos on Monterey without problem.
This package will read Photos databases for any supported version on any supported macOS version. E.g. you can read a database created with Photos 5.0 on MacOS 10.15 on a machine running macOS 10.12 and vice versa.
Requires python >= `3.8`.
@@ -97,6 +103,12 @@ Once you've installed osxphotos via the git repository, to upgrade to the latest
You can also download a stand-alone pre-built executable--that doesn't require installing python--from the [releases](https://github.com/RhetTbull/osxphotos/releases) page. Look for the file with a name similar to `osxphotos_MacOS_exe_darwin_x64_v0.42.9.zip`. In this case `v0.42.9` specifies version 0.42.9. Unzip the file and put the included `osxphotos` binary in your system path. Currently, the binary is not signed or notarized so you'll have to authorize the app to run in the System Preferences | Security & Privacy settings. It's also likely this executable will not run on M1 Macs. If you don't know how to do this, I recommend using `pipx` as described above.
## Getting Help
OSXPhotos is well documented. See the [tutorial](#tutorial) for a description of key features. The tutorial can be accessed using the command `osxphotos tutorial` via the command line. If you are interested in using OSXPhotos in your own code, see [API_README.md](https://github.com/RhetTbull/osxphotos/blob/master/API_README.md) for a description of the API as well as the [example](https://github.com/RhetTbull/osxphotos/tree/master/examples) programs. The full documentation is [available online](https://rhettbull.github.io/osxphotos/) and can also be accessed using the command `osxphotos docs` via the command line.
If you have questions, would like to show off projects created with OSXPhotos, or if you just want to say hello, please use the [GitHub discussions forum](https://github.com/RhetTbull/osxphotos/discussions) or the [osxphotos subreddit](https://www.reddit.com/r/osxphotos/) on Reddit.
## Command Line Usage
This package will install a command line utility called `osxphotos` that allows you to query the Photos database. Alternatively, you can also run the command line utility like this: `python3 -m osxphotos`
@@ -130,12 +142,14 @@ Commands:
export Export photos from the Photos database.
exportdb Utilities for working with the osxphotos export database
help Print help; for help on commands: help <command>.
import Import photos and videos into Photos.
info Print out descriptive info of the Photos library database.
inspect Interactively inspect photos selected in Photos.
install Install Python packages into the same environment as osxphotos
keywords Print out keywords found in the Photos library.
labels Print out image classification labels found in the Photos...
list Print list of Photos libraries found on the system.
orphans Find orphaned photos in a Photos library
persons Print out persons (faces) found in the Photos library.
places Print out places found in the Photos library.
query Query the Photos database using 1 or more search options; if...
@@ -252,6 +266,22 @@ the exported files would be:
/path/to/export/Travel/IMG_1234.JPG
/path/to/export/Vacation/IMG_1234.JPG
If your photos are organized in folders and albums in Photos you can preserve this structure on export by using the `{folder_album}` template field with the `--directory` option. For example, if you have a photo in the album `Vacation` which is in the `Travel` folder, the following command would export the photo to the `Travel/Vacation` directory:
`osxphotos export /path/to/export --directory "{folder_album}"`
Photos can belong to more than one album. In this case, the template field `{folder_album}` will expand to all the album names that the photo belongs to. For example, if a photo belongs to the albums `Vacation` and `Travel`, the template field `{folder_album}` would expand to `Vacation`, `Travel`. If the photo belongs to no albums, the template field `{folder_album}` would expand to "_" (the default value).
All template fields including `{folder_album}` can be further filtered using a number of different filters. To convert all directory names to lower case for example, use the `lower` filter:
`osxphotos export /path/to/export --directory "{folder_album|lower}"`
If all your photos were organized into various albums under a folder named `Events` but some where also included in other top-level albums and you wanted to export only the `Events` folder, you could use the `filter` option to filter out the other top-level albums by selecting only those folder/album paths that start with `Events`:
`osxphotos export /path/to/export --directory "{folder_album|filter(startswith Events)}"`
You can learn more about the other filters using `osxphotos help export`.
#### Specify exported filename
By default, osxphotos will use the original filename of the photo when exporting. That is, the filename the photo had when it was taken or imported into Photos. This is often something like `IMG_1234.JPG` or `DSC05678.dng`. osxphotos allows you to specify a custom filename template using the `--filename` option in the same way as `--directory` allows you to specify a custom directory name. For example, Photos allows you specify a title or caption for a photo and you can use this in place of the original filename:
@@ -431,6 +461,14 @@ You can use the `--report` option to create a report, in comma-separated values
`osxphotos export /path/to/export --report export.csv`
You can also create reports in JSON or SQLite format by changing the extension of the report filename. For example, to create a JSON report:
`osxphotos export /path/to/export --report export.json`
And to create a SQLite report:
`osxphotos export /path/to/export --report export.sqlite`
#### Exporting only certain photos
By default, osxphotos will export your entire Photos library. If you want to export only certain photos, osxphotos provides a rich set of "query options" that allow you to query the Photos database to filter out only certain photos that match your query criteria. The tutorial does not cover all the query options as there are over 50 of them--read the help text (`osxphotos help export`) to better understand the available query options. No matter which subset of photos you would like to export, there is almost certainly a way for osxphotos to filter these. For example, you can filter for only images that contain certain keywords or images without a title, images from a specific time of day or specific date range, images contained in specific albums, etc.
@@ -763,6 +801,9 @@ Options:
--is-reference Search for photos that were imported as
referenced files (not copied into Photos
library).
--not-reference Search for photos that are not references,
that is, they were copied into the Photos
library and are managed by Photos.
--in-album Search for photos that are in one or more
albums.
--not-in-album Search for photos that are not in any albums.
@@ -1040,6 +1081,10 @@ Options:
--exiftool-merge-persons Merge any persons found in the original file
with persons used for '--exiftool' and '--
sidecar'.
--favorite-rating When used with --exiftool or --sidecar, set
XMP:Rating=5 for photos marked as Favorite and
XMP:Rating=0 for non-Favorites. If not
specified, XMP:Rating is not set.
--ignore-date-modified If used with --exiftool or --sidecar, will
ignore the photo modification date and set
EXIF:ModifyDate to EXIF:DateTimeOriginal; this
@@ -1180,6 +1225,29 @@ Options:
you intend before using --cleanup. Use --dry-
run with --cleanup first if you're not
certain.
--keep KEEP_PATH When used with --cleanup, prevents file or
directory KEEP_PATH from being deleted when
cleanup is run. Use this if there are files in
the export directory that you don't want to be
deleted when --cleanup is run. KEEP_PATH may
be a file path, e.g.
'/Volumes/Photos/keep.jpg', or a file path and
wild card, e.g. '/Volumes/Photos/*.txt', or a
directory, e.g. '/Volumes/Photos/KeepMe'.
KEEP_PATH may be an absolute path or a
relative path. If it is relative, it must be
relative to the export destination. For
example if export destination is
`/Volumes/Photos` and you want to keep all
`.txt` files, you can specify `--keep
"/Volumes/Photos/*.txt"` or `--keep "*.txt"`.
If wild card is used, KEEP_PATH must be
enclosed in quotes to prevent the shell from
expanding the wildcard, e.g. `--keep
"/Volumes/Photos/*.txt"`. If KEEP_PATH is a
directory, all files and directories contained
in KEEP_PATH will be kept. --keep may be
repeated to keep additional files/directories.
--add-exported-to-album ALBUM Add all exported photos to album ALBUM in
Photos. Album ALBUM will be created if it
doesn't exist. All exported photos will be
@@ -1273,6 +1341,13 @@ Options:
--config-only If specified, saves the config file but does
not export any files; must be used with
--save-config.
--print TEMPLATE Render TEMPLATE string for each photo being
exported and print to stdout. TEMPLATE is an
osxphotos template string. This may be useful
for creating custom reports, etc. TEMPLATE
will be printed after the photo is exported or
skipped. May be repeated to print multiple
template strings.
--theme THEME Specify the color theme to use for --verbose
output. Valid themes are 'dark', 'light',
'mono', and 'plain'. Defaults to 'dark' or
@@ -1463,15 +1538,35 @@ Valid filters are:
• reverse: Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
• uniq: Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b',
'c'].
• join(x): Join list of values with delimiter x, e.g. join(:): ['a', 'b',
'c'] => 'a:b:c'; the DELIM option functions similar to join(x) but with
DELIM, the join happens before being passed to any filters.
• join(x): Join list of values with delimiter x, e.g. join(,): ['a', 'b',
'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with
DELIM, the join happens before being passed to any filters.May optionally
be used without an argument, that is 'join()' which joins values together
with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.
• append(x): Append x to list of values, e.g. append(d): ['a', 'b', 'c'] =>
['a', 'b', 'c', 'd'].
• prepend(x): Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c']
=> ['d', 'a', 'b', 'c'].
• remove(x): Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] =>
['a', 'c'].
• slice(start:stop:step): Slice list using same semantics as Python's list
slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2):
['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] =>
['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c'];
slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also
sslice().
• sslice(start:stop:step): [s(tring) slice] Slice values in a list using same
semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc';
sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().
• filter(x): Filter list of values using predicate x; for example,
{folder_album|filter(contains Events)} returns only folders/albums
containing the word 'Events' in their path.
• int: Convert values in list to integer, e.g. 1.0 => 1. If value cannot be
converted to integer, remove value from list. ['1.1', 'x'] => ['1']. See
also float.
• float: Convert values in list to floating point number, e.g. 1 => 1.0. If
value cannot be converted to float, remove value from list. ['1', 'x'] =>
['1.0']. See also int.
e.g. if Photo keywords are ["FOO","bar"]:
@@ -1827,6 +1922,10 @@ Substitution Description
identifier (UUID) for the photo, a
36-character string unique to the photo,
e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'
{shortuuid} A shorter representation of photo's internal
universally unique identifier (UUID) for the
photo, a 22-character string unique to the
photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'
{id} A unique number for the photo based on its
primary key in the Photos database. A
sequential integer, e.g. 1, 2, 3...etc.
@@ -1895,8 +1994,9 @@ Substitution Description
{newline} A newline: '\n'
{lf} A line feed: '\n', alias for {newline}
{cr} A carriage return: '\r'
{crlf} a carriage return + line feed: '\r\n'
{osxphotos_version} The osxphotos version, e.g. '0.50.1'
{crlf} A carriage return + line feed: '\r\n'
{tab} :A tab: '\t'
{osxphotos_version} The osxphotos version, e.g. '0.51.4'
{osxphotos_cmd_line} The full command line used to run osxphotos
The following substitutions may result in multiple values. Thus if specified
@@ -2180,10 +2280,15 @@ Valid filters are:
- `rsort`: Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
- `reverse`: Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
- `uniq`: Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].
- `join(x)`: Join list of values with delimiter x, e.g. join(:): ['a', 'b', 'c'] => 'a:b:c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.
- `join(x)`: Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.
- `append(x)`: Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].
- `prepend(x)`: Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].
- `remove(x)`: Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].
- `slice(start:stop:step)`: Slice list using same semantics as Python's list slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().
- `sslice(start:stop:step)`: [s(tring) slice] Slice values in a list using same semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().
- `filter(x)`: Filter list of values using predicate x; for example, `{folder_album|filter(contains Events)}` returns only folders/albums containing the word 'Events' in their path.
- `int`: Convert values in list to integer, e.g. 1.0 => 1. If value cannot be converted to integer, remove value from list. ['1.1', 'x'] => ['1']. See also float.
- `float`: Convert values in list to floating point number, e.g. 1 => 1.0. If value cannot be converted to float, remove value from list. ['1', 'x'] => ['1.0']. See also int.
e.g. if Photo keywords are `["FOO","bar"]`:
@@ -2353,6 +2458,7 @@ The following template field substitutions are availabe for use the templating s
|{exif.lens_model}|Lens model from original photo's EXIF information as imported by Photos, e.g. 'iPhone 6s back camera 4.15mm f/2.2'|
|{moment}|The moment title of the photo|
|{uuid}|Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'|
|{shortuuid}|A shorter representation of photo's internal universally unique identifier (UUID) for the photo, a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'|
|{id}|A unique number for the photo based on its primary key in the Photos database. A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{id:05d}' which results in 00001, 00002, 00003...etc. |
|{album_seq}|An integer, starting at 0, indicating the photo's index (sequence) in the containing album. Only valid when used in a '--filename' template and only when '{album}' or '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{album_seq}_{original_name}"'. To start counting at a value other than 0, append append '(starting_value)' to the field name. For example, to start counting at 1 instead of 0: '{album_seq(1)}'. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{album_seq:05d}' which results in 00000, 00001, 00002...etc. To format while also using a starting value: '{album_seq:05d(1)}' which results in 0001, 00002...etc.This may result in incorrect sequences if you have duplicate albums with the same name; see also '{folder_album_seq}'.|
|{folder_album_seq}|An integer, starting at 0, indicating the photo's index (sequence) in the containing album and folder path. Only valid when used in a '--filename' template and only when '{folder_album}' is used in the '--directory' template. For example '--directory "{folder_album}" --filename "{folder_album_seq}_{original_name}"'. To start counting at a value other than 0, append '(starting_value)' to the field name. For example, to start counting at 1 instead of 0: '{folder_album_seq(1)}' May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{folder_album_seq:05d}' which results in 00000, 00001, 00002...etc. To format while also using a starting value: '{folder_album_seq:05d(1)}' which results in 0001, 00002...etc.This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also '{album_seq}'. |
@@ -2369,8 +2475,9 @@ The following template field substitutions are availabe for use the templating s
|{newline}|A newline: '\n'|
|{lf}|A line feed: '\n', alias for {newline}|
|{cr}|A carriage return: '\r'|
|{crlf}|a carriage return + line feed: '\r\n'|
|{osxphotos_version}|The osxphotos version, e.g. '0.50.1'|
|{crlf}|A carriage return + line feed: '\r\n'|
|{tab}|:A tab: '\t'|
|{osxphotos_version}|The osxphotos version, e.g. '0.51.4'|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|{album}|Album(s) photo is contained in|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|
@@ -2395,244 +2502,6 @@ The following template field substitutions are availabe for use the templating s
|{function}|Execute a python function from an external file and use return value as template substitution. Use in format: {function:file.py::function_name} where 'file.py' is the name of the python file and 'function_name' is the name of the function to call. The function will be passed the PhotoInfo object for the photo. See https://github.com/RhetTbull/osxphotos/blob/master/examples/template_function.py for an example of how to implement a template function.|
<!-- OSXPHOTOS-TEMPLATE-TABLE:END -->
### <a name="exiftoolExifTool">ExifTool</a>
osxphotos includes its own `exiftool` library that can be accessed via `osxphotos.exiftool`:
```python
>>> from osxphotos.exiftool import ExifTool
>>> exiftool = ExifTool("/Users/rhet/Downloads/test.jpeg")
>>> exifdict = exiftool.asdict()
>>> exifdict["EXIF:Make"]
'Canon'
>>> exiftool.setvalue("IPTC:Keywords","Keyword1")
True
>>> exiftool.asdict()["IPTC:Keywords"]
'Keyword1'
>>> exiftool.addvalues("IPTC:Keywords","Keyword2","Keyword3")
True
>>> exiftool.asdict()["IPTC:Keywords"]
['Keyword1', 'Keyword2', 'Keyword3']
```
`ExifTool(filepath, exiftool=None)`
* `filepath`: str, path to photo
* `exiftool`: str, optional path to `exiftool`; if not provided, will look for `exiftool` in the system path
#### ExifTool methods
* `asdict(tag_groups=True)`: 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. If `tag_groups` is True (default) dict keys are in form "GROUP:TAG", e.g. "IPTC:Keywords". If `tag_groups` is False, dict keys do not have group names, e.g. "Keywords".
```python
{'Composite:Aperture': 2.2,
'Composite:GPSPosition': '-34.9188916666667 138.596861111111',
'Composite:ImageSize': '2754 2754',
'EXIF:CreateDate': '2017:06:20 17:18:56',
'EXIF:LensMake': 'Apple',
'EXIF:LensModel': 'iPhone 6s back camera 4.15mm f/2.2',
'EXIF:Make': 'Apple',
'XMP:Title': 'Elder Park',
}
```
* `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
photo.exiftool.setvalue("XMP:Title", "Title of photo")
```
* `addvalues(tag, *values)`: Add one or more value(s) to tag. For a tag that accepts multiple values, like "IPTC:Keywords", this will add the values as additional list values. However, for tags which are not usually lists, such as "EXIF:ISO" this will literally add the new value to the old value which is probably not the desired effect. Be sure you understand the behavior of the individual tag before using this. For example:
```python
photo.exiftool.addvalues("IPTC:Keywords", "vacation", "beach")
```
osxphotos.exiftool also provides an `ExifToolCaching` class which caches all metadata after the first call to `exiftool`. This can significantly speed up repeated access to the metadata but should only be used if you do not intend to modify the file's metadata.
[`PhotoInfo.exiftool`](#exiftool) returns an `ExifToolCaching` instance for the original image in the Photos library.
#### Implementation Note
`ExifTool()` runs `exiftool` as a subprocess using the `-stay_open True` flag to keep the process running in the background. The subprocess will be cleaned up when your main script terminates. `ExifTool()` uses a singleton pattern to ensure that only one instance of `exiftool` is created. Multiple instances of `ExifTool()` will all use the same `exiftool` subprocess.
### <a name="photoexporter">PhotoExporter</a>
[PhotoInfo.export()](#photoinfo) provides a simple method to export a photo. This method actually calls `PhotoExporter.export()` to do the export. `PhotoExporter` provides many more options to configure the export and report results and this is what the osxphotos command line export tools uses.
#### `export(dest, filename=None, options: Optional[ExportOptions]=None) -> ExportResults`
Export a photo.
Args:
* dest: must be valid destination path or exception raised
* filename: (optional): name of exported picture; if not provided, will use current filename
* options (ExportOptions): optional ExportOptions instance
Returns: ExportResults instance
*Note*: to use dry run mode, you must set options.dry_run=True and also pass in memory version of export_db, and no-op fileutil (e.g. `ExportDBInMemory` and `FileUtilNoOp`) in options.export_db and options.fileutil respectively.
#### `ExportOptions`
Options class for exporting photos with `export`
Attributes:
* convert_to_jpeg (bool): if True, converts non-jpeg images to jpeg
* description_template (str): optional template string that will be rendered for use as photo description
* download_missing: (bool, default=False): if True will attempt to export photo via applescript interaction with Photos if missing (see also use_photokit, use_photos_export)
* dry_run: (bool, default=False): set to True to run in "dry run" mode
* edited: (bool, default=False): if True will export the edited version of the photo otherwise exports the original version
* exiftool_flags (list of str): optional list of flags to pass to exiftool when using exiftool option, e.g ["-m", "-F"]
* exiftool: (bool, default = False): if True, will use exiftool to write metadata to export file
* export_as_hardlink: (bool, default=False): if True, will hardlink files instead of copying them
* export_db: (ExportDB): instance of a class that conforms to ExportDB with methods for getting/setting data related to exported files to compare update state
* fileutil: (FileUtilABC): class that conforms to FileUtilABC with various file utilities
* ignore_date_modified (bool): for use with sidecar and exiftool; if True, sets EXIF:ModifyDate to EXIF:DateTimeOriginal even if date_modified is set
* ignore_signature (bool, default=False): ignore file signature when used with update (look only at filename)
* increment (bool, default=True): if True, will increment file name until a non-existant name is found if overwrite=False and increment=False, export will fail if destination file already exists
* jpeg_ext (str): if set, will use this value for extension on jpegs converted to jpeg with convert_to_jpeg; if not set, uses jpeg; do not include the leading "."
* jpeg_quality (float in range 0.0 <= jpeg_quality <= 1.0): a value of 1.0 specifies use best quality, a value of 0.0 specifies use maximum compression.
* keyword_template (list of str): list of template strings that will be rendered as used as keywords
* live_photo (bool, default=False): if True, will also export the associated .mov for live photos
* location (bool): if True, include location in exported metadata
* merge_exif_keywords (bool): if True, merged keywords found in file's exif data (requires exiftool)
* merge_exif_persons (bool): if True, merged persons found in file's exif data (requires exiftool)
* overwrite (bool, default=False): if True will overwrite files if they already exist
* persons (bool): if True, include persons in exported metadata
* preview_suffix (str): optional string to append to end of filename for preview images
* preview (bool): if True, also exports preview image
* raw_photo (bool, default=False): if True, will also export the associated RAW photo
* render_options (RenderOptions): optional osxphotos.phototemplate.RenderOptions instance to specify options for rendering templates
* replace_keywords (bool): if True, keyword_template replaces any keywords, otherwise it's additive
* sidecar_drop_ext (bool, default=False): if True, drops the photo's extension from sidecar filename (e.g. 'IMG_1234.json' instead of 'IMG_1234.JPG.json')
* sidecar: bit field (int): set to one or more of SIDECAR_XMP, SIDECAR_JSON, SIDECAR_EXIFTOOL
* SIDECAR_JSON: if set will write a json sidecar with data in format readable by exiftool sidecar filename will be dest/filename.json; includes exiftool tag group names (e.g. `exiftool -G -j`)
* SIDECAR_EXIFTOOL: if set will write a json sidecar with data in format readable by exiftool sidecar filename will be dest/filename.json; does not include exiftool tag group names (e.g. `exiftool -j`)
* SIDECAR_XMP: if set will write an XMP sidecar with IPTC data sidecar filename will be dest/filename.xmp
* strip (bool): if True, strip whitespace from rendered templates
* timeout (int, default=120): timeout in seconds used with use_photos_export
* touch_file (bool, default=False): if True, sets file's modification time upon photo date
* update (bool, default=False): if True export will run in update mode, that is, it will not export the photo if the current version already exists in the destination
* use_albums_as_keywords (bool, default = False): if True, will include album names in keywords when exporting metadata with exiftool or sidecar
* use_persons_as_keywords (bool, default = False): if True, will include person names in keywords when exporting metadata with exiftool or sidecar
* use_photos_export (bool, default=False): if True will attempt to export photo via applescript interaction with Photos even if not missing (see also use_photokit, download_missing)
* use_photokit (bool, default=False): if True, will use photokit to export photos when use_photos_export is True
* verbose (Callable): optional callable function to use for printing verbose text during processing; if None (default), does not print output.
* tmpfile (str): optional path to use for temporary files
#### `ExportResults`
`PhotoExporter().export()` returns an instance of this class.
`ExportResults` has the following properties:
* datetime: date/time of export in ISO 8601 format
* exported: list of all exported files (A single call to export could export more than one file, e.g. original file, preview, live video, raw, etc.)
* new: list of new files exported when used with update=True
* updated: list of updated files when used with update=True
* skipped: list of skipped files when used with update=True
* exif_updated: list of updated files when used with update=True and exiftool
* touched: list of files touched during export (e.g. file date/time updated with touch_file=True)
* to_touch: Reserved for internal use of export
* converted_to_jpeg: list of files converted to jpeg when convert_to_jpeg=True
* sidecar_json_written: list of JSON sidecars written
* sidecar_json_skipped: list of JSON sidecars skipped when update=True
* sidecar_exiftool_written: list of exiftool sidecars written
* sidecar_exiftool_skipped: list of exiftool sidecars skipped when update=True
* sidecar_xmp_written: list of XMP sidecars written
* sidecar_xmp_skipped: list of XMP sidecars skipped when update=True
* missing: list of missing files
* error: list of tuples containing (filename, error) if error generated during export
* exiftool_warning: list of warnings generated by exiftool during export
* exiftool_error: list of errors generated by exiftool during export
* xattr_written: list of files with extended attributes written during export
* xattr_skipped: list of files where extended attributes were skipped when update=True
* metadata_changed: list of files where metadata changed since last export
* deleted_files: reserved for use by osxphotos CLI
* deleted_directories: reserved for use by osxphotos CLI
* exported_album: reserved for use by osxphotos CLI
* skipped_album: reserved for use by osxphotos CLI
* missing_album: reserved for use by osxphotos CLI
### <a name="textdetection">Text Detection</a>
The [PhotoInfo.detected_text()](#detected_text_method) and the `{detected_text}` template will perform text detection on the photos in your library. Text detection is a slow process so to avoid unnecessary re-processing of photos, osxphotos will cache the results of the text detection process as an extended attribute on the photo image file. Extended attributes do not modify the actual file. The extended attribute is named `osxphotos.metadata:detected_text` and can be viewed using the built-in [xattr](https://ss64.com/osx/xattr.html) command or my [osxmetadata](https://github.com/RhetTbull/osxmetadata) tool. If you want to remove the cached attribute, you can do so with osxmetadata as follows:
`osxmetadata --clear osxphotos.metadata:detected_text --walk ~/Pictures/Photos\ Library.photoslibrary/`
### Utility Functions
The following functions are located in osxphotos.utils
#### `get_system_library_path()`
**MacOS 10.15 Only** Returns path to System Photo Library as string. On MacOS version < 10.15, returns None.
#### `get_last_library_path()`
Returns path to last opened Photo Library as string.
#### `list_photo_libraries()`
Returns list of Photos libraries found on the system. **Note**: On MacOS 10.15, this appears to list all libraries. On older systems, it may not find some libraries if they are not located in ~/Pictures. Provided for convenience but do not rely on this to find all libraries on the system.
## Examples
```python
import osxphotos
def main():
photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Photos Library.photoslibrary")
print(f"db file = {photosdb.db_path}")
print(f"db version = {photosdb.db_version}")
print(photosdb.keywords)
print(photosdb.persons)
print(photosdb.album_names)
print(photosdb.keywords_as_dict)
print(photosdb.persons_as_dict)
print(photosdb.albums_as_dict)
# find all photos with Keyword = Kids and containing person Katie
photos = photosdb.photos(keywords=["Kids"], persons=["Katie"])
print(f"found {len(photos)} photos")
# find all photos that include Katie but do not contain the keyword wedding
photos = [
p
for p in photosdb.photos(persons=["Katie"])
if p not in photosdb.photos(keywords=["wedding"])
]
# get all photos in the database
photos = photosdb.photos()
for p in photos:
print(
p.uuid,
p.filename,
p.date,
p.description,
p.title,
p.keywords,
p.albums,
p.persons,
p.path,
p.ismissing,
p.hasadjustments,
)
if __name__ == "__main__":
main()
```
## Related Projects
* [rhettbull/exif2findertags](https://github.com/RhetTbull/exif2findertags): Read EXIF metadata from image and video files and convert it to macOS Finder tags and/or Finder comments and other extended attributes.
@@ -2701,6 +2570,14 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/neebah"><img src="https://avatars.githubusercontent.com/u/71442026?v=4?s=75" width="75px;" alt=""/><br /><sub><b>neebah</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Aneebah" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/ahti123"><img src="https://avatars.githubusercontent.com/u/22232632?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Ahti Liin</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=ahti123" title="Code">💻</a> <a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Aahti123" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/xwu64"><img src="https://avatars.githubusercontent.com/u/10580396?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Xiaoliang Wu</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=xwu64" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/nullpointerninja"><img src="https://avatars.githubusercontent.com/u/62975432?v=4?s=75" width="75px;" alt=""/><br /><sub><b>nullpointerninja</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Anullpointerninja" title="Bug reports">🐛</a> <a href="#ideas-nullpointerninja" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/infused-kim"><img src="https://avatars.githubusercontent.com/u/7404004?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Kim</b></sub></a><br /><a href="#ideas-infused-kim" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/Se7enair"><img src="https://avatars.githubusercontent.com/u/1680106?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Christoph</b></sub></a><br /><a href="#ideas-Se7enair" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://www.franzone.com"><img src="https://avatars.githubusercontent.com/u/900684?v=4?s=75" width="75px;" alt=""/><br /><sub><b>franzone</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Afranzone" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://jmuccigr.github.io/"><img src="https://avatars.githubusercontent.com/u/615115?v=4?s=75" width="75px;" alt=""/><br /><sub><b>John Muccigrosso</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Ajmuccigr" title="Bug reports">🐛</a> <a href="#ideas-jmuccigr" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://nomadgate.com"><img src="https://avatars.githubusercontent.com/u/1646041?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Thomas K. Running</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=tkrunning" title="Code">💻</a> <a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Atkrunning" title="Bug reports">🐛</a></td>
</tr>
</table>

View File

@@ -1,9 +1,11 @@
build
bump2version==1.0.1
cogapp>=3.3.0,<4.0.0
furo
m2r2
pdbpp
pyinstaller==4.10
pytest-cov==3.0.0
pytest-mock
pytest==7.0.1
Sphinx

View File

@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: d9acd3739acb05851cda9121cea01662
config: 9c14761ce21d96c98ba51262e03cfe2d
tags: 645f666f9bcd5a90fca523b33c5a78b7

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../genindex.html" /><link rel="search" title="Search" href="../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>Overview: module code - osxphotos 0.50.1 documentation</title>
<title>Overview: module code - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="../index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -205,6 +205,7 @@
<li><a href="osxphotos/personinfo.html">osxphotos.personinfo</a></li>
<li><a href="osxphotos/photoexporter.html">osxphotos.photoexporter</a></li>
<li><a href="osxphotos/photoinfo.html">osxphotos.photoinfo</a></li>
<li><a href="osxphotos/photosalbum.html">osxphotos.photosalbum</a></li>
<li><a href="osxphotos/photosdb/_photosdb_process_comments.html">osxphotos.photosdb._photosdb_process_comments</a></li>
<li><a href="osxphotos/photosdb/photosdb.html">osxphotos.photosdb.photosdb</a></li>
<li><a href="osxphotos/phototemplate.html">osxphotos.phototemplate</a></li>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos._constants - osxphotos 0.48.1 documentation</title>
<title>osxphotos._constants - osxphotos 0.51.0 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.48.1 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.51.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.48.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -240,10 +240,8 @@
<span class="c1"># Ranges for model version by Photos version</span>
<span class="n">_PHOTOS_5_MODEL_VERSION</span> <span class="o">=</span> <span class="p">[</span><span class="mi">13000</span><span class="p">,</span> <span class="mi">13999</span><span class="p">]</span>
<span class="n">_PHOTOS_6_MODEL_VERSION</span> <span class="o">=</span> <span class="p">[</span><span class="mi">14000</span><span class="p">,</span> <span class="mi">14999</span><span class="p">]</span>
<span class="n">_PHOTOS_7_MODEL_VERSION</span> <span class="o">=</span> <span class="p">[</span>
<span class="mi">15000</span><span class="p">,</span>
<span class="mi">15999</span><span class="p">,</span>
<span class="p">]</span> <span class="c1"># Monterey developer preview is 15134, 12.1 is 15331</span>
<span class="n">_PHOTOS_7_MODEL_VERSION</span> <span class="o">=</span> <span class="p">[</span><span class="mi">15000</span><span class="p">,</span> <span class="mi">15999</span><span class="p">]</span> <span class="c1"># Dev preview: 15134, 12.1: 15331</span>
<span class="n">_PHOTOS_8_MODEL_VERSION</span> <span class="o">=</span> <span class="p">[</span><span class="mi">16000</span><span class="p">,</span> <span class="mi">16999</span><span class="p">]</span> <span class="c1"># Ventura dev preview: 16119</span>
<span class="c1"># some table names differ between Photos 5 and Photos 6</span>
<span class="n">_DB_TABLE_NAMES</span> <span class="o">=</span> <span class="p">{</span>
@@ -283,6 +281,18 @@
<span class="s2">"ASSET_ALBUM_TABLE"</span><span class="p">:</span> <span class="s2">"Z_27ASSETS"</span><span class="p">,</span>
<span class="s2">"HDR_TYPE"</span><span class="p">:</span> <span class="s2">"ZHDRTYPE"</span><span class="p">,</span>
<span class="p">},</span>
<span class="mi">8</span><span class="p">:</span> <span class="p">{</span>
<span class="s2">"ASSET"</span><span class="p">:</span> <span class="s2">"ZASSET"</span><span class="p">,</span>
<span class="s2">"KEYWORD_JOIN"</span><span class="p">:</span> <span class="s2">"Z_1KEYWORDS.Z_40KEYWORDS"</span><span class="p">,</span>
<span class="s2">"ALBUM_JOIN"</span><span class="p">:</span> <span class="s2">"Z_28ASSETS.Z_3ASSETS"</span><span class="p">,</span>
<span class="s2">"ALBUM_SORT_ORDER"</span><span class="p">:</span> <span class="s2">"Z_28ASSETS.Z_FOK_3ASSETS"</span><span class="p">,</span>
<span class="s2">"IMPORT_FOK"</span><span class="p">:</span> <span class="s2">"null"</span><span class="p">,</span>
<span class="s2">"DEPTH_STATE"</span><span class="p">:</span> <span class="s2">"ZASSET.ZDEPTHTYPE"</span><span class="p">,</span>
<span class="s2">"UTI_ORIGINAL"</span><span class="p">:</span> <span class="s2">"ZINTERNALRESOURCE.ZCOMPACTUTI"</span><span class="p">,</span>
<span class="s2">"ASSET_ALBUM_JOIN"</span><span class="p">:</span> <span class="s2">"Z_28ASSETS.Z_28ALBUMS"</span><span class="p">,</span>
<span class="s2">"ASSET_ALBUM_TABLE"</span><span class="p">:</span> <span class="s2">"Z_28ASSETS"</span><span class="p">,</span>
<span class="s2">"HDR_TYPE"</span><span class="p">:</span> <span class="s2">"ZHDRTYPE"</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="c1"># which version operating systems have been tested</span>
@@ -303,6 +313,8 @@
<span class="p">(</span><span class="s2">"12"</span><span class="p">,</span> <span class="s2">"1"</span><span class="p">),</span>
<span class="p">(</span><span class="s2">"12"</span><span class="p">,</span> <span class="s2">"2"</span><span class="p">),</span>
<span class="p">(</span><span class="s2">"12"</span><span class="p">,</span> <span class="s2">"3"</span><span class="p">),</span>
<span class="p">(</span><span class="s2">"12"</span><span class="p">,</span> <span class="s2">"4"</span><span class="p">),</span>
<span class="p">(</span><span class="s2">"12"</span><span class="p">,</span> <span class="s2">"5"</span><span class="p">),</span>
<span class="p">]</span>
<span class="c1"># Photos 5 has persons who are empty string if unidentified face</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.exiftool - osxphotos 0.49.0 documentation</title>
<title>osxphotos.exiftool - osxphotos 0.50.13 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.49.0 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.50.13 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.49.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.50.13 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -202,7 +202,9 @@
<span class="sd"> If these aren't important to you, I highly recommend you use Sven Marnach's excellent </span>
<span class="sd"> pyexiftool: https://github.com/smarnach/pyexiftool which provides more functionality """</span>
<span class="kn">import</span> <span class="nn">atexit</span>
<span class="kn">import</span> <span class="nn">contextlib</span>
<span class="kn">import</span> <span class="nn">html</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">logging</span>
@@ -300,9 +302,12 @@
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">instance</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">large_file_support</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
<span class="sd">"""construct _ExifToolProc singleton object or return instance of already created object</span>
<span class="sd"> exiftool: optional path to exiftool binary (if not provided, will search path to find it)</span>
<span class="sd"> Args:</span>
<span class="sd"> exiftool: optional path to exiftool binary (if not provided, will search path to find it)</span>
<span class="sd"> large_file_support: if True, enables large file support (&gt;4GB) via `-api largefilesupport=1`</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s2">"_process_running"</span><span class="p">)</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span><span class="p">:</span>
@@ -315,16 +320,14 @@
<span class="k">return</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span> <span class="o">=</span> <span class="kc">False</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_exiftool</span> <span class="o">=</span> <span class="n">exiftool</span> <span class="ow">or</span> <span class="n">get_exiftool_path</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_start_proc</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_start_proc</span><span class="p">(</span><span class="n">large_file_support</span><span class="o">=</span><span class="n">large_file_support</span><span class="p">)</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">process</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""return the exiftool subprocess"""</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_start_proc</span><span class="p">()</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">pid</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
@@ -336,7 +339,7 @@
<span class="sd">"""return path to exiftool process"""</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_exiftool</span>
<span class="k">def</span> <span class="nf">_start_proc</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">_start_proc</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">large_file_support</span><span class="p">):</span>
<span class="sd">"""start exiftool in batch mode"""</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span><span class="p">:</span>
@@ -347,11 +350,13 @@
<span class="c1"># make sure /usr/bin at start of path so exiftool can find xattr (see #636)</span>
<span class="n">env</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
<span class="n">env</span><span class="p">[</span><span class="s2">"PATH"</span><span class="p">]</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">'/usr/bin/:</span><span class="si">{</span><span class="n">env</span><span class="p">[</span><span class="s2">"PATH"</span><span class="p">]</span><span class="si">}</span><span class="s1">'</span>
<span class="n">large_file_args</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"-api"</span><span class="p">,</span> <span class="s2">"largefilesupport=1"</span><span class="p">]</span> <span class="k">if</span> <span class="n">large_file_support</span> <span class="k">else</span> <span class="p">[]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">Popen</span><span class="p">(</span>
<span class="p">[</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_exiftool</span><span class="p">,</span>
<span class="s2">"-stay_open"</span><span class="p">,</span> <span class="c1"># keep process open in batch mode</span>
<span class="s2">"True"</span><span class="p">,</span> <span class="c1"># -stay_open=True, keep process open in batch mode</span>
<span class="o">*</span><span class="n">large_file_args</span><span class="p">,</span>
<span class="s2">"-@"</span><span class="p">,</span> <span class="c1"># read command-line arguments from file</span>
<span class="s2">"-"</span><span class="p">,</span> <span class="c1"># read from stdin</span>
<span class="s2">"-common_args"</span><span class="p">,</span> <span class="c1"># specifies args common to all commands subsequently run</span>
@@ -375,13 +380,10 @@
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_running</span><span class="p">:</span>
<span class="k">return</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">with</span> <span class="n">contextlib</span><span class="o">.</span><span class="n">suppress</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">b</span><span class="s2">"-stay_open</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="sa">b</span><span class="s2">"False</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">pass</span>
<span class="k">try</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process</span><span class="o">.</span><span class="n">communicate</span><span class="p">(</span><span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
<span class="k">except</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">TimeoutExpired</span><span class="p">:</span>
@@ -395,7 +397,14 @@
<div class="viewcode-block" id="ExifTool"><a class="viewcode-back" href="../../reference.html#osxphotos.ExifTool">[docs]</a><span class="k">class</span> <span class="nc">ExifTool</span><span class="p">:</span>
<span class="sd">"""Basic exiftool interface for reading and writing EXIF tags"""</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">filepath</span><span class="p">,</span> <span class="n">exiftool</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">overwrite</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">flags</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span>
<span class="n">filepath</span><span class="p">,</span>
<span class="n">exiftool</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">overwrite</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">flags</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">large_file_support</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="p">):</span>
<span class="sd">"""Create ExifTool object</span>
<span class="sd"> Args:</span>
@@ -403,6 +412,7 @@
<span class="sd"> exiftool: path to exiftool, if not specified will look in path</span>
<span class="sd"> overwrite: if True, will overwrite image file without creating backup, default=False</span>
<span class="sd"> flags: optional list of exiftool flags to prepend to exiftool command when writing metadata (e.g. -m or -F)</span>
<span class="sd"> large_file_support: if True, enables large file support in exiftool (`-api largefilesupport=1`)</span>
<span class="sd"> Returns:</span>
<span class="sd"> ExifTool instance</span>
@@ -415,7 +425,9 @@
<span class="bp">self</span><span class="o">.</span><span class="n">error</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># if running as a context manager, self._context_mgr will be True</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_context_mgr</span> <span class="o">=</span> <span class="kc">False</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_exiftoolproc</span> <span class="o">=</span> <span class="n">_ExifToolProc</span><span class="p">(</span><span class="n">exiftool</span><span class="o">=</span><span class="n">exiftool</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_exiftoolproc</span> <span class="o">=</span> <span class="n">_ExifToolProc</span><span class="p">(</span>
<span class="n">exiftool</span><span class="o">=</span><span class="n">exiftool</span><span class="p">,</span> <span class="n">large_file_support</span><span class="o">=</span><span class="n">large_file_support</span>
<span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_read_exif</span><span class="p">()</span>
<span class="nd">@property</span>
@@ -523,7 +535,7 @@
<span class="n">commands</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">commands</span><span class="p">)</span>
<span class="n">commands</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s2">"-overwrite_original"</span><span class="p">)</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">fsencode</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">file</span><span class="p">)</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">no_file</span> <span class="k">else</span> <span class="sa">b</span><span class="s2">""</span>
<span class="n">filename</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">""</span> <span class="k">if</span> <span class="n">no_file</span> <span class="k">else</span> <span class="n">os</span><span class="o">.</span><span class="n">fsencode</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">file</span><span class="p">)</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">flags</span><span class="p">:</span>
<span class="c1"># need to split flags, e.g. so "--ext AVI" becomes ["--ext", "AVI"]</span>
@@ -619,8 +631,7 @@
<span class="k">def</span> <span class="nf">_read_exif</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""read exif data from file"""</span>
<span class="n">data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="n">v</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">data</span><span class="o">.</span><span class="n">items</span><span class="p">()}</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">()</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"file: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">file</span><span class="si">}</span><span class="se">\n</span><span class="s2">exiftool: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_exiftoolproc</span><span class="o">.</span><span class="n">_exiftool</span><span class="si">}</span><span class="s2">"</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.export_db - osxphotos 0.50.1 documentation</title>
<title>osxphotos.export_db - osxphotos 0.50.3 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.50.3 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.50.3 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -1076,7 +1076,7 @@
<span class="bp">self</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">ExportDBTemp</span><span class="p">(</span><span class="n">ExportDBInMemory</span><span class="p">):</span>
<div class="viewcode-block" id="ExportDBTemp"><a class="viewcode-back" href="../../reference.html#osxphotos.ExportDBTemp">[docs]</a><span class="k">class</span> <span class="nc">ExportDBTemp</span><span class="p">(</span><span class="n">ExportDBInMemory</span><span class="p">):</span>
<span class="sd">"""Temporary in-memory version of ExportDB"""</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
@@ -1091,7 +1091,7 @@
<span class="n">filepath</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
<span class="k">if</span> <span class="n">filepath</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/"</span><span class="p">:</span>
<span class="k">return</span> <span class="n">filepath</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="k">return</span> <span class="n">filepath</span>
<span class="k">return</span> <span class="n">filepath</span></div>
<span class="k">class</span> <span class="nc">ExportRecord</span><span class="p">:</span>

View File

@@ -1,37 +1,200 @@
<!doctype html>
<html class="no-js">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<!DOCTYPE html>
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.personinfo - osxphotos 0.50.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos.personinfo &#8212; osxphotos 0.47.9 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/alabaster.css" />
<script data-url_root="../../" id="documentation_options" src="../../_static/documentation_options.js"></script>
<script src="../../_static/jquery.js"></script>
<script src="../../_static/underscore.js"></script>
<script src="../../_static/doctools.js"></script>
<link rel="index" title="Index" href="../../genindex.html" />
<link rel="search" title="Search" href="../../search.html" />
<link rel="stylesheet" href="../../_static/custom.css" type="text/css" />
<style>
body {
--color-code-background: #f8f8f8;
--color-code-foreground: black;
}
@media not print {
body[data-theme="dark"] {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
}
@media (prefers-color-scheme: dark) {
body:not([data-theme="light"]) {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
}
}
}
</style></head>
<body>
<script>
document.body.dataset.theme = localStorage.getItem("theme") || "auto";
</script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-toc" viewBox="0 0 24 24">
<title>Contents</title>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 1024 1024">
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z"/>
</svg>
</symbol>
<symbol id="svg-menu" viewBox="0 0 24 24">
<title>Menu</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-menu">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</symbol>
<symbol id="svg-arrow-right" viewBox="0 0 24 24">
<title>Expand</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24">
<title>Light mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather-sun">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24">
<title>Dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-moon">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
</svg>
</symbol>
<symbol id="svg-sun-half" viewBox="0 0 24 24">
<title>Auto light/dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-shadow">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="12" cy="12" r="9" />
<path d="M13 12h5" />
<path d="M13 15h4" />
<path d="M13 18h1" />
<path d="M13 9h4" />
<path d="M13 6h1" />
</svg>
</symbol>
</svg>
<input type="checkbox" class="sidebar-toggle" name="__navigation" id="__navigation">
<input type="checkbox" class="sidebar-toggle" name="__toc" id="__toc">
<label class="overlay sidebar-overlay" for="__navigation">
<div class="visually-hidden">Hide navigation sidebar</div>
</label>
<label class="overlay toc-overlay" for="__toc">
<div class="visually-hidden">Hide table of contents sidebar</div>
</label>
<div class="page">
<header class="mobile-header">
<div class="header-left">
<label class="nav-overlay-icon" for="__navigation">
<div class="visually-hidden">Toggle site navigation sidebar</div>
<i class="icon"><svg><use href="#svg-menu"></use></svg></i>
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.50.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-header-icon no-toc" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
</label>
</div>
</header>
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
</head><body>
<span class="sidebar-brand-text">osxphotos 0.50.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
<input type="hidden" name="check_keywords" value="yes">
<input type="hidden" name="area" value="default">
</form>
<div id="searchbox"></div><div class="sidebar-scroll"><div class="sidebar-tree">
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../overview.html">OSXPhotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../tutorial.html">OSXPhotos Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../cli.html">OSXPhotos Command Line Interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../template_help.html">OSXPhotos Template System</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../package_overview.html">OSXPhotos Python Package Overview</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../reference.html">OSXPhotos python API</a></li>
</ul>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
</div>
</div>
<div class="body" role="main">
<h1>Source code for osxphotos.personinfo</h1><div class="highlight"><pre>
<span></span><span class="sd">&quot;&quot;&quot; PhotoInfo and FaceInfo classes to expose info about persons and faces in the Photos library &quot;&quot;&quot;</span>
</div>
</div>
</aside>
<div class="main">
<div class="content">
<div class="article-container">
<a href="#" class="back-to-top muted-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z"></path>
</svg>
<span>Back to top</span>
</a>
<div class="content-icon-container"><div class="theme-toggle-container theme-toggle-content">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-content-icon no-toc" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
</label>
</div>
<article role="main">
<h1>Source code for osxphotos.personinfo</h1><div class="highlight"><pre>
<span></span><span class="sd">""" PhotoInfo and FaceInfo classes to expose info about persons and faces in the Photos library """</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">logging</span>
@@ -39,17 +202,17 @@
<span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">namedtuple</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;PersonInfo&quot;</span><span class="p">,</span> <span class="s2">&quot;FaceInfo&quot;</span><span class="p">,</span> <span class="s2">&quot;rotate_image_point&quot;</span><span class="p">]</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"PersonInfo"</span><span class="p">,</span> <span class="s2">"FaceInfo"</span><span class="p">,</span> <span class="s2">"rotate_image_point"</span><span class="p">]</span>
<span class="n">MWG_RS_Area</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s2">&quot;MWG_RS_Area&quot;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&quot;x&quot;</span><span class="p">,</span> <span class="s2">&quot;y&quot;</span><span class="p">,</span> <span class="s2">&quot;h&quot;</span><span class="p">,</span> <span class="s2">&quot;w&quot;</span><span class="p">])</span>
<span class="n">MPRI_Reg_Rect</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s2">&quot;MPRI_Reg_Rect&quot;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&quot;x&quot;</span><span class="p">,</span> <span class="s2">&quot;y&quot;</span><span class="p">,</span> <span class="s2">&quot;h&quot;</span><span class="p">,</span> <span class="s2">&quot;w&quot;</span><span class="p">])</span>
<span class="n">MWG_RS_Area</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s2">"MWG_RS_Area"</span><span class="p">,</span> <span class="p">[</span><span class="s2">"x"</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">,</span> <span class="s2">"h"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">])</span>
<span class="n">MPRI_Reg_Rect</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s2">"MPRI_Reg_Rect"</span><span class="p">,</span> <span class="p">[</span><span class="s2">"x"</span><span class="p">,</span> <span class="s2">"y"</span><span class="p">,</span> <span class="s2">"h"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">])</span>
<div class="viewcode-block" id="PersonInfo"><a class="viewcode-back" href="../../reference.html#osxphotos.PersonInfo">[docs]</a><span class="k">class</span> <span class="nc">PersonInfo</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Info about a person in the Photos library&quot;&quot;&quot;</span>
<span class="sd">"""Info about a person in the Photos library"""</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">db</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">pk</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Creates a new PersonInfo instance</span>
<span class="sd">"""Creates a new PersonInfo instance</span>
<span class="sd"> Arguments:</span>
<span class="sd"> db: instance of PhotosDB object</span>
@@ -57,16 +220,16 @@
<span class="sd"> Returns:</span>
<span class="sd"> PersonInfo instance</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_db</span> <span class="o">=</span> <span class="n">db</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_pk</span> <span class="o">=</span> <span class="n">pk</span>
<span class="n">person</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_dbpersons_pk</span><span class="p">[</span><span class="n">pk</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">&quot;uuid&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">&quot;fullname&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">display_name</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">&quot;displayname&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">keyface</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">&quot;keyface&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">facecount</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">&quot;facecount&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"uuid"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"fullname"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">display_name</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"displayname"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">keyface</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"keyface"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">facecount</span> <span class="o">=</span> <span class="n">person</span><span class="p">[</span><span class="s2">"facecount"</span><span class="p">]</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">keyphoto</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
@@ -74,9 +237,9 @@
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_keyphoto</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="n">person</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_dbpersons_pk</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="p">]</span>
<span class="k">if</span> <span class="n">person</span><span class="p">[</span><span class="s2">&quot;photo_uuid&quot;</span><span class="p">]:</span>
<span class="k">if</span> <span class="n">person</span><span class="p">[</span><span class="s2">"photo_uuid"</span><span class="p">]:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">key_photo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">get_photo</span><span class="p">(</span><span class="n">person</span><span class="p">[</span><span class="s2">&quot;photo_uuid&quot;</span><span class="p">])</span>
<span class="n">key_photo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">get_photo</span><span class="p">(</span><span class="n">person</span><span class="p">[</span><span class="s2">"photo_uuid"</span><span class="p">])</span>
<span class="k">except</span> <span class="ne">IndexError</span><span class="p">:</span>
<span class="n">key_photo</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">else</span><span class="p">:</span>
@@ -86,14 +249,14 @@
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">photos</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Returns list of PhotoInfo objects associated with this person&quot;&quot;&quot;</span>
<span class="sd">"""Returns list of PhotoInfo objects associated with this person"""</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">photos_by_uuid</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_dbfaces_pk</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="p">])</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">face_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Returns a list of FaceInfo objects associated with this person sorted by quality score</span>
<span class="sd">"""Returns a list of FaceInfo objects associated with this person sorted by quality score</span>
<span class="sd"> Highest quality face is result[0] and lowest quality face is result[n]</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">faces</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_faceinfo_person</span><span class="p">[</span><span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="p">]</span>
<span class="k">return</span> <span class="nb">sorted</span><span class="p">(</span>
@@ -106,30 +269,32 @@
<span class="k">return</span> <span class="p">[]</span>
<div class="viewcode-block" id="PersonInfo.asdict"><a class="viewcode-back" href="../../reference.html#osxphotos.PersonInfo.asdict">[docs]</a> <span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Returns dictionary representation of class instance&quot;&quot;&quot;</span>
<span class="sd">"""Returns dictionary representation of class instance"""</span>
<span class="n">keyphoto</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">keyphoto</span><span class="o">.</span><span class="n">uuid</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">keyphoto</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s2">&quot;uuid&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
<span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
<span class="s2">&quot;displayname&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">display_name</span><span class="p">,</span>
<span class="s2">&quot;keyface&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">keyface</span><span class="p">,</span>
<span class="s2">&quot;facecount&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">facecount</span><span class="p">,</span>
<span class="s2">&quot;keyphoto&quot;</span><span class="p">:</span> <span class="n">keyphoto</span><span class="p">,</span>
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
<span class="s2">"name"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
<span class="s2">"displayname"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">display_name</span><span class="p">,</span>
<span class="s2">"keyface"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">keyface</span><span class="p">,</span>
<span class="s2">"facecount"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">facecount</span><span class="p">,</span>
<span class="s2">"keyphoto"</span><span class="p">:</span> <span class="n">keyphoto</span><span class="p">,</span>
<span class="p">}</span></div>
<div class="viewcode-block" id="PersonInfo.json"><a class="viewcode-back" href="../../reference.html#osxphotos.PersonInfo.json">[docs]</a> <span class="k">def</span> <span class="nf">json</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Returns JSON representation of class instance&quot;&quot;&quot;</span>
<span class="sd">"""Returns JSON representation of class instance"""</span>
<span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">())</span></div>
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">&quot;PersonInfo(name=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">, display_name=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">display_name</span><span class="si">}</span><span class="s2">, uuid=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2">, facecount=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">facecount</span><span class="si">}</span><span class="s2">)&quot;</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"PersonInfo(name=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">, display_name=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">display_name</span><span class="si">}</span><span class="s2">, uuid=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2">, facecount=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">facecount</span><span class="si">}</span><span class="s2">)"</span>
<span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)):</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="k">return</span> <span class="nb">all</span><span class="p">(</span>
<span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="k">for</span> <span class="n">field</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&quot;_db&quot;</span><span class="p">,</span> <span class="s2">&quot;_pk&quot;</span><span class="p">]</span>
<span class="k">return</span> <span class="p">(</span>
<span class="nb">all</span><span class="p">(</span>
<span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span>
<span class="k">for</span> <span class="n">field</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"_db"</span><span class="p">,</span> <span class="s2">"_pk"</span><span class="p">]</span>
<span class="p">)</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">))</span>
<span class="k">else</span> <span class="kc">False</span>
<span class="p">)</span>
<span class="k">def</span> <span class="fm">__ne__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
@@ -137,10 +302,10 @@
<span class="k">class</span> <span class="nc">FaceInfo</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Info about a face in the Photos library&quot;&quot;&quot;</span>
<span class="sd">"""Info about a face in the Photos library"""</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">db</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">pk</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Creates a new FaceInfo instance</span>
<span class="sd">"""Creates a new FaceInfo instance</span>
<span class="sd"> Arguments:</span>
<span class="sd"> db: instance of PhotosDB object</span>
@@ -148,95 +313,59 @@
<span class="sd"> Returns:</span>
<span class="sd"> FaceInfo instance</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_db</span> <span class="o">=</span> <span class="n">db</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_pk</span> <span class="o">=</span> <span class="n">pk</span>
<span class="n">face</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_faceinfo_pk</span><span class="p">[</span><span class="n">pk</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_info</span> <span class="o">=</span> <span class="n">face</span>
<span class="bp">self</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;uuid&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;fullname&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;asset_uuid&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_person_pk</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;person&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">center_x</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;centerx&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">center_y</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;centery&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">mouth_x</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;mouthx&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">mouth_y</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;mouthy&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">left_eye_x</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;lefteyex&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">left_eye_y</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;lefteyey&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">right_eye_x</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;righteyex&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">right_eye_y</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;righteyey&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">size</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;size&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">quality</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;quality&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">source_width</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;sourcewidth&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">source_height</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;sourceheight&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">has_smile</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;has_smile&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">left_eye_closed</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;left_eye_closed&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">right_eye_closed</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;right_eye_closed&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">manual</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;manual&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">face_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;facetype&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">age_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;agetype&quot;</span><span class="p">]</span>
<span class="c1"># self.bald_type = face[&quot;baldtype&quot;]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">eye_makeup_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;eyemakeuptype&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">eye_state</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;eyestate&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">facial_hair_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;facialhairtype&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">gender_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;gendertype&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">glasses_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;glassestype&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">hair_color_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;haircolortype&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">intrash</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;intrash&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">lip_makeup_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;lipmakeuptype&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">smile_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">&quot;smiletype&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">uuid</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"uuid"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"fullname"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"asset_uuid"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_person_pk</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"person"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">center_x</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"centerx"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">center_y</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"centery"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">size</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"size"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">quality</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"quality"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">source_width</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"sourcewidth"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">source_height</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"sourceheight"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">has_smile</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"has_smile"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">manual</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"manual"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">face_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"facetype"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">age_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"agetype"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">eye_makeup_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"eyemakeuptype"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">eye_state</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"eyestate"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">facial_hair_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"facialhairtype"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">gender_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"gendertype"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">glasses_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"glassestype"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">hair_color_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"haircolortype"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">intrash</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"intrash"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">lip_makeup_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"lipmakeuptype"</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">smile_type</span> <span class="o">=</span> <span class="n">face</span><span class="p">[</span><span class="s2">"smiletype"</span><span class="p">]</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">center</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Coordinates, in PIL format, for center of face</span>
<span class="sd">"""Coordinates, in PIL format, for center of face</span>
<span class="sd"> Returns:</span>
<span class="sd"> tuple of coordinates in form (x, y)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_make_point</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_y</span><span class="p">))</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">size_pixels</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Size of face in pixels (centered around center_x, center_y)</span>
<span class="sd">"""Size of face in pixels (centered around center_x, center_y)</span>
<span class="sd"> Returns:</span>
<span class="sd"> size, in int pixels, of a circle drawn around the center of the face</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="n">photo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span>
<span class="n">size_reference</span> <span class="o">=</span> <span class="n">photo</span><span class="o">.</span><span class="n">width</span> <span class="k">if</span> <span class="n">photo</span><span class="o">.</span><span class="n">width</span> <span class="o">&gt;</span> <span class="n">photo</span><span class="o">.</span><span class="n">height</span> <span class="k">else</span> <span class="n">photo</span><span class="o">.</span><span class="n">height</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">size</span> <span class="o">*</span> <span class="n">size_reference</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">mouth</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Coordinates, in PIL format, for mouth position</span>
<span class="sd"> Returns:</span>
<span class="sd"> tuple of coordinates in form (x, y)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_make_point_with_rotation</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">mouth_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">mouth_y</span><span class="p">))</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">left_eye</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Coordinates, in PIL format, for left eye position</span>
<span class="sd"> Returns:</span>
<span class="sd"> tuple of coordinates in form (x, y)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_make_point_with_rotation</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">left_eye_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">left_eye_y</span><span class="p">))</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">right_eye</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Coordinates, in PIL format, for right eye position</span>
<span class="sd"> Returns:</span>
<span class="sd"> tuple of coordinates in form (x, y)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_make_point_with_rotation</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">right_eye_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">right_eye_y</span><span class="p">))</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">person_info</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;PersonInfo instance for person associated with this face&quot;&quot;&quot;</span>
<span class="sd">"""PersonInfo instance for person associated with this face"""</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_person</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
@@ -245,18 +374,18 @@
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">photo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;PhotoInfo instance associated with this face&quot;&quot;&quot;</span>
<span class="sd">"""PhotoInfo instance associated with this face"""</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_photo</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_photo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">get_photo</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="p">)</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_photo</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Could not get photo for uuid: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Could not get photo for uuid: </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_photo</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">mwg_rs_area</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Get coordinates for Metadata Working Group Region Area.</span>
<span class="sd">"""Get coordinates for Metadata Working Group Region Area.</span>
<span class="sd"> Returns:</span>
<span class="sd"> MWG_RS_Area named tuple with x, y, h, w where:</span>
@@ -267,7 +396,7 @@
<span class="sd"> Reference:</span>
<span class="sd"> https://photo.stackexchange.com/questions/106410/how-does-xmp-define-the-face-region</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_y</span>
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fix_orientation</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
@@ -282,7 +411,7 @@
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">mpri_reg_rect</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Get coordinates for Microsoft Photo Region Rectangle.</span>
<span class="sd">"""Get coordinates for Microsoft Photo Region Rectangle.</span>
<span class="sd"> Returns:</span>
<span class="sd"> MPRI_Reg_Rect named tuple with x, y, h, w where:</span>
@@ -293,7 +422,7 @@
<span class="sd"> Reference:</span>
<span class="sd"> https://docs.microsoft.com/en-us/windows/win32/wic/-wic-people-tagging</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_y</span>
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_fix_orientation</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
@@ -311,13 +440,13 @@
<span class="k">return</span> <span class="n">MPRI_Reg_Rect</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">w</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">face_rect</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Get face rectangle coordinates for current version of the associated image</span>
<span class="sd">"""Get face rectangle coordinates for current version of the associated image</span>
<span class="sd"> If image has been edited, rectangle applies to edited version, otherwise original version</span>
<span class="sd"> Coordinates in format and reference frame used by PIL</span>
<span class="sd"> Returns:</span>
<span class="sd"> list [(x0, x1), (y0, y1)] of coordinates in reference frame used by PIL</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="n">photo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span>
<span class="n">size_reference</span> <span class="o">=</span> <span class="n">photo</span><span class="o">.</span><span class="n">width</span> <span class="k">if</span> <span class="n">photo</span><span class="o">.</span><span class="n">width</span> <span class="o">&gt;</span> <span class="n">photo</span><span class="o">.</span><span class="n">height</span> <span class="k">else</span> <span class="n">photo</span><span class="o">.</span><span class="n">height</span>
<span class="n">radius</span> <span class="o">=</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">size</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span> <span class="o">*</span> <span class="n">size_reference</span>
@@ -327,34 +456,34 @@
<span class="k">return</span> <span class="p">[(</span><span class="n">x0</span><span class="p">,</span> <span class="n">y0</span><span class="p">),</span> <span class="p">(</span><span class="n">x1</span><span class="p">,</span> <span class="n">y1</span><span class="p">)]</span>
<span class="k">def</span> <span class="nf">roll_pitch_yaw</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Roll, pitch, yaw of face in radians as tuple&quot;&quot;&quot;</span>
<span class="sd">"""Roll, pitch, yaw of face in radians as tuple"""</span>
<span class="n">info</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span>
<span class="n">roll</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">&quot;roll&quot;</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">&quot;roll&quot;</span><span class="p">]</span>
<span class="n">pitch</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">&quot;pitch&quot;</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">&quot;pitch&quot;</span><span class="p">]</span>
<span class="n">yaw</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">&quot;yaw&quot;</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">&quot;yaw&quot;</span><span class="p">]</span>
<span class="n">roll</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">"roll"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">"roll"</span><span class="p">]</span>
<span class="n">pitch</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">"pitch"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">"pitch"</span><span class="p">]</span>
<span class="n">yaw</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">info</span><span class="p">[</span><span class="s2">"yaw"</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span> <span class="k">else</span> <span class="n">info</span><span class="p">[</span><span class="s2">"yaw"</span><span class="p">]</span>
<span class="k">return</span> <span class="p">(</span><span class="n">roll</span><span class="p">,</span> <span class="n">pitch</span><span class="p">,</span> <span class="n">yaw</span><span class="p">)</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">roll</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return roll angle in radians of the face region&quot;&quot;&quot;</span>
<span class="sd">"""Return roll angle in radians of the face region"""</span>
<span class="n">roll</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">roll_pitch_yaw</span><span class="p">()</span>
<span class="k">return</span> <span class="n">roll</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">pitch</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return pitch angle in radians of the face region&quot;&quot;&quot;</span>
<span class="sd">"""Return pitch angle in radians of the face region"""</span>
<span class="n">_</span><span class="p">,</span> <span class="n">pitch</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">roll_pitch_yaw</span><span class="p">()</span>
<span class="k">return</span> <span class="n">pitch</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">yaw</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return yaw angle in radians of the face region&quot;&quot;&quot;</span>
<span class="sd">"""Return yaw angle in radians of the face region"""</span>
<span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">yaw</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">roll_pitch_yaw</span><span class="p">()</span>
<span class="k">return</span> <span class="n">yaw</span>
<span class="k">def</span> <span class="nf">_fix_orientation</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">xy</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Translate an (x, y) tuple based on image orientation</span>
<span class="sd">"""Translate an (x, y) tuple based on image orientation</span>
<span class="sd"> Arguments:</span>
<span class="sd"> xy: tuple of (x, y) coordinates for point to translate</span>
@@ -362,7 +491,7 @@
<span class="sd"> Returns:</span>
<span class="sd"> (x, y) tuple of translated coordinates</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="c1"># Reference: https://github.com/neilpa/phace/blob/7594776480505d0c389688a42099c94ac5d34f3f/cmd/phace/draw.go#L79-L94</span>
<span class="n">orientation</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">orientation</span>
@@ -386,15 +515,15 @@
<span class="k">elif</span> <span class="n">orientation</span> <span class="o">==</span> <span class="mi">8</span><span class="p">:</span>
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">y</span><span class="p">,</span> <span class="n">x</span>
<span class="k">elif</span> <span class="n">orientation</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="c1"># set by osxphotos if adjusted orientation cannot be read, assume it&#39;s 1</span>
<span class="c1"># set by osxphotos if adjusted orientation cannot be read, assume it's 1</span>
<span class="n">y</span> <span class="o">=</span> <span class="mf">1.0</span> <span class="o">-</span> <span class="n">y</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Unhandled orientation: </span><span class="si">{</span><span class="n">orientation</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Unhandled orientation: </span><span class="si">{</span><span class="n">orientation</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">return</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">_make_point</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">xy</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Translate an (x, y) tuple based on image orientation</span>
<span class="sd">"""Translate an (x, y) tuple based on image orientation</span>
<span class="sd"> and convert to image coordinates</span>
<span class="sd"> Arguments:</span>
@@ -403,7 +532,7 @@
<span class="sd"> Returns:</span>
<span class="sd"> (x, y) tuple of translated coordinates in pixels in PIL format/reference frame</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="c1"># Reference: https://github.com/neilpa/phace/blob/7594776480505d0c389688a42099c94ac5d34f3f/cmd/phace/draw.go#L79-L94</span>
<span class="n">orientation</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">orientation</span>
@@ -415,7 +544,7 @@
<span class="k">return</span> <span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">x</span> <span class="o">*</span> <span class="n">dx</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">y</span> <span class="o">*</span> <span class="n">dy</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">_make_point_with_rotation</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">xy</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Translate an (x, y) tuple based on image orientation and rotation</span>
<span class="sd">"""Translate an (x, y) tuple based on image orientation and rotation</span>
<span class="sd"> and convert to image coordinates</span>
<span class="sd"> Arguments:</span>
@@ -424,7 +553,7 @@
<span class="sd"> Returns:</span>
<span class="sd"> (x, y) tuple of translated coordinates in pixels in PIL format/reference frame</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="c1"># convert to image coordinates</span>
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_make_point</span><span class="p">(</span><span class="n">xy</span><span class="p">)</span>
@@ -437,70 +566,58 @@
<span class="k">return</span> <span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">xr</span><span class="p">),</span> <span class="nb">int</span><span class="p">(</span><span class="n">yr</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Returns dict representation of class instance&quot;&quot;&quot;</span>
<span class="sd">"""Returns dict representation of class instance"""</span>
<span class="n">roll</span><span class="p">,</span> <span class="n">pitch</span><span class="p">,</span> <span class="n">yaw</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">roll_pitch_yaw</span><span class="p">()</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s2">&quot;_pk&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="p">,</span>
<span class="s2">&quot;uuid&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
<span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
<span class="s2">&quot;asset_uuid&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="p">,</span>
<span class="s2">&quot;_person_pk&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_person_pk</span><span class="p">,</span>
<span class="s2">&quot;center_x&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="p">,</span>
<span class="s2">&quot;center_y&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_y</span><span class="p">,</span>
<span class="s2">&quot;center&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center</span><span class="p">,</span>
<span class="s2">&quot;mouth_x&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mouth_x</span><span class="p">,</span>
<span class="s2">&quot;mouth_y&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mouth_y</span><span class="p">,</span>
<span class="s2">&quot;mouth&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mouth</span><span class="p">,</span>
<span class="s2">&quot;left_eye_x&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">left_eye_x</span><span class="p">,</span>
<span class="s2">&quot;left_eye_y&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">left_eye_y</span><span class="p">,</span>
<span class="s2">&quot;left_eye&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">left_eye</span><span class="p">,</span>
<span class="s2">&quot;right_eye_x&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">right_eye_x</span><span class="p">,</span>
<span class="s2">&quot;right_eye_y&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">right_eye_y</span><span class="p">,</span>
<span class="s2">&quot;right_eye&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">right_eye</span><span class="p">,</span>
<span class="s2">&quot;size&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">size</span><span class="p">,</span>
<span class="s2">&quot;face_rect&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">face_rect</span><span class="p">(),</span>
<span class="s2">&quot;mpri_reg_rect&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mpri_reg_rect</span><span class="o">.</span><span class="n">_asdict</span><span class="p">(),</span>
<span class="s2">&quot;mwg_rs_area&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mwg_rs_area</span><span class="o">.</span><span class="n">_asdict</span><span class="p">(),</span>
<span class="s2">&quot;roll&quot;</span><span class="p">:</span> <span class="n">roll</span><span class="p">,</span>
<span class="s2">&quot;pitch&quot;</span><span class="p">:</span> <span class="n">pitch</span><span class="p">,</span>
<span class="s2">&quot;yaw&quot;</span><span class="p">:</span> <span class="n">yaw</span><span class="p">,</span>
<span class="s2">&quot;quality&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">quality</span><span class="p">,</span>
<span class="s2">&quot;source_width&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">source_width</span><span class="p">,</span>
<span class="s2">&quot;source_height&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">source_height</span><span class="p">,</span>
<span class="s2">&quot;has_smile&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">has_smile</span><span class="p">,</span>
<span class="s2">&quot;left_eye_closed&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">left_eye_closed</span><span class="p">,</span>
<span class="s2">&quot;right_eye_closed&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">right_eye_closed</span><span class="p">,</span>
<span class="s2">&quot;manual&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">manual</span><span class="p">,</span>
<span class="s2">&quot;face_type&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">face_type</span><span class="p">,</span>
<span class="s2">&quot;age_type&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">age_type</span><span class="p">,</span>
<span class="c1"># &quot;bald_type&quot;: self.bald_type,</span>
<span class="s2">&quot;eye_makeup_type&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">eye_makeup_type</span><span class="p">,</span>
<span class="s2">&quot;eye_state&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">eye_state</span><span class="p">,</span>
<span class="s2">&quot;facial_hair_type&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">facial_hair_type</span><span class="p">,</span>
<span class="s2">&quot;gender_type&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">gender_type</span><span class="p">,</span>
<span class="s2">&quot;glasses_type&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">glasses_type</span><span class="p">,</span>
<span class="s2">&quot;hair_color_type&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hair_color_type</span><span class="p">,</span>
<span class="s2">&quot;intrash&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">intrash</span><span class="p">,</span>
<span class="s2">&quot;lip_makeup_type&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">lip_makeup_type</span><span class="p">,</span>
<span class="s2">&quot;smile_type&quot;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">smile_type</span><span class="p">,</span>
<span class="s2">"_pk"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="p">,</span>
<span class="s2">"uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="p">,</span>
<span class="s2">"name"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
<span class="s2">"asset_uuid"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="p">,</span>
<span class="s2">"_person_pk"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">_person_pk</span><span class="p">,</span>
<span class="s2">"center_x"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="p">,</span>
<span class="s2">"center_y"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center_y</span><span class="p">,</span>
<span class="s2">"center"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">center</span><span class="p">,</span>
<span class="s2">"size"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">size</span><span class="p">,</span>
<span class="s2">"face_rect"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">face_rect</span><span class="p">(),</span>
<span class="s2">"mpri_reg_rect"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mpri_reg_rect</span><span class="o">.</span><span class="n">_asdict</span><span class="p">(),</span>
<span class="s2">"mwg_rs_area"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">mwg_rs_area</span><span class="o">.</span><span class="n">_asdict</span><span class="p">(),</span>
<span class="s2">"roll"</span><span class="p">:</span> <span class="n">roll</span><span class="p">,</span>
<span class="s2">"pitch"</span><span class="p">:</span> <span class="n">pitch</span><span class="p">,</span>
<span class="s2">"yaw"</span><span class="p">:</span> <span class="n">yaw</span><span class="p">,</span>
<span class="s2">"quality"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">quality</span><span class="p">,</span>
<span class="s2">"source_width"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">source_width</span><span class="p">,</span>
<span class="s2">"source_height"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">source_height</span><span class="p">,</span>
<span class="s2">"has_smile"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">has_smile</span><span class="p">,</span>
<span class="s2">"manual"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">manual</span><span class="p">,</span>
<span class="s2">"face_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">face_type</span><span class="p">,</span>
<span class="s2">"age_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">age_type</span><span class="p">,</span>
<span class="s2">"eye_makeup_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">eye_makeup_type</span><span class="p">,</span>
<span class="s2">"eye_state"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">eye_state</span><span class="p">,</span>
<span class="s2">"facial_hair_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">facial_hair_type</span><span class="p">,</span>
<span class="s2">"gender_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">gender_type</span><span class="p">,</span>
<span class="s2">"glasses_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">glasses_type</span><span class="p">,</span>
<span class="s2">"hair_color_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">hair_color_type</span><span class="p">,</span>
<span class="s2">"intrash"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">intrash</span><span class="p">,</span>
<span class="s2">"lip_makeup_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">lip_makeup_type</span><span class="p">,</span>
<span class="s2">"smile_type"</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">smile_type</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">def</span> <span class="nf">json</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return JSON representation of FaceInfo instance&quot;&quot;&quot;</span>
<span class="sd">"""Return JSON representation of FaceInfo instance"""</span>
<span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">asdict</span><span class="p">())</span>
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">&quot;FaceInfo(uuid=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2">, center_x=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="si">}</span><span class="s2">, center_y = </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">center_y</span><span class="si">}</span><span class="s2">, size=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">size</span><span class="si">}</span><span class="s2">, person=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">, asset_uuid=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="si">}</span><span class="s2">)&quot;</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"FaceInfo(uuid=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2">, center_x=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">center_x</span><span class="si">}</span><span class="s2">, center_y = </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">center_y</span><span class="si">}</span><span class="s2">, size=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">size</span><span class="si">}</span><span class="s2">, person=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">, asset_uuid=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">asset_uuid</span><span class="si">}</span><span class="s2">)"</span>
<span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">&quot;FaceInfo(db=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="si">}</span><span class="s2">, pk=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="si">}</span><span class="s2">)&quot;</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"FaceInfo(db=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="si">}</span><span class="s2">, pk=</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_pk</span><span class="si">}</span><span class="s2">)"</span>
<span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)):</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="k">return</span> <span class="nb">all</span><span class="p">(</span>
<span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="k">for</span> <span class="n">field</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">&quot;_db&quot;</span><span class="p">,</span> <span class="s2">&quot;_pk&quot;</span><span class="p">]</span>
<span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="o">==</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">other</span><span class="p">,</span> <span class="n">field</span><span class="p">)</span> <span class="k">for</span> <span class="n">field</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"_db"</span><span class="p">,</span> <span class="s2">"_pk"</span><span class="p">]</span>
<span class="p">)</span>
<span class="k">def</span> <span class="fm">__ne__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
@@ -508,7 +625,7 @@
<span class="k">def</span> <span class="nf">rotate_image_point</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">xmid</span><span class="p">,</span> <span class="n">ymid</span><span class="p">,</span> <span class="n">angle</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;rotate image point about xm, ym by angle in radians</span>
<span class="sd">"""rotate image point about xm, ym by angle in radians</span>
<span class="sd"> Arguments:</span>
<span class="sd"> x: x coordinate of point to rotate</span>
@@ -520,7 +637,7 @@
<span class="sd"> Returns:</span>
<span class="sd"> tuple of rotated points (xr, yr)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="c1"># translate point relative to the mid point</span>
<span class="n">x</span> <span class="o">=</span> <span class="n">x</span> <span class="o">-</span> <span class="n">xmid</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">y</span> <span class="o">-</span> <span class="n">ymid</span>
@@ -535,72 +652,45 @@
<span class="k">return</span> <span class="p">(</span><span class="n">xr</span><span class="p">,</span> <span class="n">yr</span><span class="p">)</span>
</pre></div>
</div>
</article>
</div>
<footer>
<div class="related-pages">
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h1 class="logo"><a href="../../index.html">osxphotos</a></h1>
<h3>Navigation</h3>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../overview.html">osxphotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../tutorial.html">Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../cli.html">osxphotos command line interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../reference.html">osxphotos package</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="../../index.html">Documentation overview</a><ul>
<li><a href="../index.html">Module code</a><ul>
</ul></li>
</ul></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3 id="searchlabel">Quick search</h3>
<div class="searchformwrapper">
<form class="search" action="../../search.html" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
<input type="submit" value="Go" />
</form>
</div>
</div>
<script>$('#searchbox').show(0);</script>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2021, Rhet Turnbull
</div>
Made with <a href="https://www.sphinx-doc.org/">Sphinx</a> and <a class="muted-link" href="https://pradyunsg.me">@pradyunsg</a>'s
<a href="https://github.com/pradyunsg/furo">Furo</a>
</div>
<div class="right-details">
<div class="icons">
</div>
</div>
</div>
</div>
<div class="clearer"></div>
</footer>
</div>
<div class="footer">
&copy;2021, Rhet Turnbull.
<aside class="toc-drawer no-toc">
|
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
&amp; <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
</div>
</body>
</aside>
</div>
</div><script data-url_root="../../" id="documentation_options" src="../../_static/documentation_options.js"></script>
<script src="../../_static/jquery.js"></script>
<script src="../../_static/underscore.js"></script>
<script src="../../_static/doctools.js"></script>
<script src="../../_static/scripts/furo.js"></script>
<script src="../../_static/clipboard.min.js"></script>
<script src="../../_static/copybutton.js"></script>
</body>
</html>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.photoexporter - osxphotos 0.50.1 documentation</title>
<title>osxphotos.photoexporter - osxphotos 0.50.13 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.50.13 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.50.13 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -332,6 +332,7 @@
<span class="sd"> use_photokit (bool, default=False): if True, will use photokit to export photos when use_photos_export is True</span>
<span class="sd"> verbose (callable): optional callable function to use for printing verbose text during processing; if None (default), does not print output.</span>
<span class="sd"> tmpdir: (str, default=None): Optional directory to use for temporary files, if None (default) uses system tmp directory</span>
<span class="sd"> favorite_rating (bool): if True, set XMP:Rating=5 for favorite images and XMP:Rating=0 for non-favorites</span>
<span class="sd"> """</span>
@@ -377,6 +378,7 @@
<span class="n">use_photos_export</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">verbose</span><span class="p">:</span> <span class="n">t</span><span class="o">.</span><span class="n">Optional</span><span class="p">[</span><span class="n">t</span><span class="o">.</span><span class="n">Callable</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">tmpdir</span><span class="p">:</span> <span class="n">t</span><span class="o">.</span><span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">favorite_rating</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">def</span> <span class="nf">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="n">asdict</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
@@ -1145,7 +1147,7 @@
<span class="c1"># export live_photo .mov file?</span>
<span class="n">live_photo</span> <span class="o">=</span> <span class="nb">bool</span><span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">live_photo</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">live_photo</span><span class="p">)</span>
<span class="n">overwrite</span> <span class="o">=</span> <span class="nb">any</span><span class="p">([</span><span class="n">options</span><span class="o">.</span><span class="n">overwrite</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">update</span><span class="p">,</span> <span class="n">options</span><span class="o">.</span><span class="n">force_update</span><span class="p">])</span>
<span class="n">edited_version</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="ow">or</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">shared</span>
<span class="n">edited_version</span> <span class="o">=</span> <span class="nb">bool</span><span class="p">(</span><span class="n">options</span><span class="o">.</span><span class="n">edited</span> <span class="ow">or</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">shared</span><span class="p">)</span>
<span class="c1"># shared photos (in shared albums) show up as not having adjustments (not edited)</span>
<span class="c1"># but Photos is unable to export the "original" as only a jpeg copy is shared in iCloud</span>
<span class="c1"># so tell Photos to export the current version in this case</span>
@@ -1782,6 +1784,7 @@
<span class="sd"> QuickTime:ModifyDate (UTC)</span>
<span class="sd"> QuickTime:GPSCoordinates</span>
<span class="sd"> UserData:GPSCoordinates</span>
<span class="sd"> XMP:Rating</span>
<span class="sd"> Reference:</span>
<span class="sd"> https://iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201610_1.pdf</span>
@@ -1897,8 +1900,8 @@
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">face_regions</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">face_info</span><span class="p">:</span>
<span class="n">exif</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_get_mwg_face_regions_exiftool</span><span class="p">())</span>
<span class="c1"># if self.favorite():</span>
<span class="c1"># exif["Rating"] = 5</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">favorite_rating</span><span class="p">:</span>
<span class="n">exif</span><span class="p">[</span><span class="s2">"XMP:Rating"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">5</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">favorite</span> <span class="k">else</span> <span class="mi">0</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">location</span><span class="p">:</span>
<span class="p">(</span><span class="n">lat</span><span class="p">,</span> <span class="n">lon</span><span class="p">)</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">location</span>
@@ -2205,6 +2208,11 @@
<span class="n">latlon</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">location</span> <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">location</span> <span class="k">else</span> <span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">favorite_rating</span><span class="p">:</span>
<span class="n">rating</span> <span class="o">=</span> <span class="mi">5</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">favorite</span> <span class="k">else</span> <span class="mi">0</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">rating</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">xmp_str</span> <span class="o">=</span> <span class="n">xmp_template</span><span class="o">.</span><span class="n">render</span><span class="p">(</span>
<span class="n">photo</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="p">,</span>
<span class="n">description</span><span class="o">=</span><span class="n">description</span><span class="p">,</span>
@@ -2214,6 +2222,7 @@
<span class="n">extension</span><span class="o">=</span><span class="n">extension</span><span class="p">,</span>
<span class="n">location</span><span class="o">=</span><span class="n">latlon</span><span class="p">,</span>
<span class="n">version</span><span class="o">=</span><span class="n">__version__</span><span class="p">,</span>
<span class="n">rating</span><span class="o">=</span><span class="n">rating</span><span class="p">,</span>
<span class="p">)</span>
<span class="c1"># remove extra lines that mako inserts from template</span>

View File

@@ -0,0 +1,373 @@
<!doctype html>
<html class="no-js">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.photosalbum - osxphotos 0.51.0 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<style>
body {
--color-code-background: #f8f8f8;
--color-code-foreground: black;
}
@media not print {
body[data-theme="dark"] {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
}
@media (prefers-color-scheme: dark) {
body:not([data-theme="light"]) {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
}
}
}
</style></head>
<body>
<script>
document.body.dataset.theme = localStorage.getItem("theme") || "auto";
</script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-toc" viewBox="0 0 24 24">
<title>Contents</title>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 1024 1024">
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z"/>
</svg>
</symbol>
<symbol id="svg-menu" viewBox="0 0 24 24">
<title>Menu</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-menu">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</symbol>
<symbol id="svg-arrow-right" viewBox="0 0 24 24">
<title>Expand</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24">
<title>Light mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather-sun">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24">
<title>Dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-moon">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
</svg>
</symbol>
<symbol id="svg-sun-half" viewBox="0 0 24 24">
<title>Auto light/dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-shadow">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="12" cy="12" r="9" />
<path d="M13 12h5" />
<path d="M13 15h4" />
<path d="M13 18h1" />
<path d="M13 9h4" />
<path d="M13 6h1" />
</svg>
</symbol>
</svg>
<input type="checkbox" class="sidebar-toggle" name="__navigation" id="__navigation">
<input type="checkbox" class="sidebar-toggle" name="__toc" id="__toc">
<label class="overlay sidebar-overlay" for="__navigation">
<div class="visually-hidden">Hide navigation sidebar</div>
</label>
<label class="overlay toc-overlay" for="__toc">
<div class="visually-hidden">Hide table of contents sidebar</div>
</label>
<div class="page">
<header class="mobile-header">
<div class="header-left">
<label class="nav-overlay-icon" for="__navigation">
<div class="visually-hidden">Toggle site navigation sidebar</div>
<i class="icon"><svg><use href="#svg-menu"></use></svg></i>
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.51.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-header-icon no-toc" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
</label>
</div>
</header>
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.51.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
<input type="hidden" name="check_keywords" value="yes">
<input type="hidden" name="area" value="default">
</form>
<div id="searchbox"></div><div class="sidebar-scroll"><div class="sidebar-tree">
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../overview.html">OSXPhotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../tutorial.html">OSXPhotos Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../cli.html">OSXPhotos Command Line Interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../template_help.html">OSXPhotos Template System</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../package_overview.html">OSXPhotos Python Package Overview</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../reference.html">OSXPhotos python API</a></li>
</ul>
</div>
</div>
</div>
</div>
</aside>
<div class="main">
<div class="content">
<div class="article-container">
<a href="#" class="back-to-top muted-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z"></path>
</svg>
<span>Back to top</span>
</a>
<div class="content-icon-container"><div class="theme-toggle-container theme-toggle-content">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-content-icon no-toc" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
</label>
</div>
<article role="main">
<h1>Source code for osxphotos.photosalbum</h1><div class="highlight"><pre>
<span></span><span class="sd">""" PhotosAlbum class to create an album in default Photos library and add photos to it """</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
<span class="kn">import</span> <span class="nn">photoscript</span>
<span class="kn">from</span> <span class="nn">more_itertools</span> <span class="kn">import</span> <span class="n">chunked</span>
<span class="kn">from</span> <span class="nn">photoscript</span> <span class="kn">import</span> <span class="n">Album</span><span class="p">,</span> <span class="n">Folder</span><span class="p">,</span> <span class="n">Photo</span><span class="p">,</span> <span class="n">PhotosLibrary</span>
<span class="kn">from</span> <span class="nn">.photoinfo</span> <span class="kn">import</span> <span class="n">PhotoInfo</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">noop</span><span class="p">,</span> <span class="n">pluralize</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"PhotosAlbum"</span><span class="p">,</span> <span class="s2">"PhotosAlbumPhotoScript"</span><span class="p">]</span>
<div class="viewcode-block" id="PhotosAlbum"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotosAlbum">[docs]</a><span class="k">class</span> <span class="nc">PhotosAlbum</span><span class="p">:</span>
<span class="sd">"""Add osxphotos.photoinfo.PhotoInfo objects to album"""</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">verbose</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">callable</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
<span class="bp">self</span><span class="o">.</span><span class="n">verbose</span> <span class="o">=</span> <span class="n">verbose</span> <span class="ow">or</span> <span class="n">noop</span>
<span class="bp">self</span><span class="o">.</span><span class="n">library</span> <span class="o">=</span> <span class="n">photoscript</span><span class="o">.</span><span class="n">PhotosLibrary</span><span class="p">()</span>
<span class="n">album</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">library</span><span class="o">.</span><span class="n">album</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
<span class="k">if</span> <span class="n">album</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Creating Photos album '</span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
<span class="n">album</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">library</span><span class="o">.</span><span class="n">create_album</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">album</span> <span class="o">=</span> <span class="n">album</span>
<span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">photo</span><span class="p">:</span> <span class="n">PhotoInfo</span><span class="p">):</span>
<span class="n">photo_</span> <span class="o">=</span> <span class="n">photoscript</span><span class="o">.</span><span class="n">Photo</span><span class="p">(</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">album</span><span class="o">.</span><span class="n">add</span><span class="p">([</span><span class="n">photo_</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">verbose</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">"Added </span><span class="si">{</span><span class="n">photo</span><span class="o">.</span><span class="n">original_filename</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2">) to album </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">add_list</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">photo_list</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">PhotoInfo</span><span class="p">]):</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photo_list</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">photos</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">photoscript</span><span class="o">.</span><span class="n">Photo</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">uuid</span><span class="p">))</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Error creating Photo object for photo </span><span class="si">{</span><span class="n">p</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">for</span> <span class="n">photolist</span> <span class="ow">in</span> <span class="n">chunked</span><span class="p">(</span><span class="n">photos</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">album</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">photolist</span><span class="p">)</span>
<span class="n">photo_len</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">photo_list</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">verbose</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">"Added </span><span class="si">{</span><span class="n">photo_len</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">pluralize</span><span class="p">(</span><span class="n">photo_len</span><span class="p">,</span> <span class="s1">'photo'</span><span class="p">,</span> <span class="s1">'photos'</span><span class="p">)</span><span class="si">}</span><span class="s2"> to album </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">photos</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">album</span><span class="o">.</span><span class="n">photos</span><span class="p">()</span></div>
<span class="k">def</span> <span class="nf">folder_by_path</span><span class="p">(</span><span class="n">folders</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">verbose</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">callable</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Folder</span><span class="p">:</span>
<span class="sd">"""Get (and create if necessary) a Photos Folder by path (passed as list of folder names)"""</span>
<span class="n">library</span> <span class="o">=</span> <span class="n">PhotosLibrary</span><span class="p">()</span>
<span class="n">verbose</span> <span class="o">=</span> <span class="n">verbose</span> <span class="ow">or</span> <span class="n">noop</span>
<span class="n">top_folder_name</span> <span class="o">=</span> <span class="n">folders</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">top_folder</span> <span class="o">=</span> <span class="n">library</span><span class="o">.</span><span class="n">folder</span><span class="p">(</span><span class="n">top_folder_name</span><span class="p">,</span> <span class="n">top_level</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">top_folder</span><span class="p">:</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Creating folder '</span><span class="si">{</span><span class="n">top_folder_name</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
<span class="n">top_folder</span> <span class="o">=</span> <span class="n">library</span><span class="o">.</span><span class="n">create_folder</span><span class="p">(</span><span class="n">top_folder_name</span><span class="p">)</span>
<span class="n">current_folder</span> <span class="o">=</span> <span class="n">top_folder</span>
<span class="k">for</span> <span class="n">folder_name</span> <span class="ow">in</span> <span class="n">folders</span><span class="p">:</span>
<span class="n">folder</span> <span class="o">=</span> <span class="n">current_folder</span><span class="o">.</span><span class="n">folder</span><span class="p">(</span><span class="n">folder_name</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">folder</span><span class="p">:</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Creating folder '</span><span class="si">{</span><span class="n">folder_name</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
<span class="n">folder</span> <span class="o">=</span> <span class="n">current_folder</span><span class="o">.</span><span class="n">create_folder</span><span class="p">(</span><span class="n">folder_name</span><span class="p">)</span>
<span class="n">current_folder</span> <span class="o">=</span> <span class="n">folder</span>
<span class="k">return</span> <span class="n">current_folder</span>
<span class="k">def</span> <span class="nf">album_by_path</span><span class="p">(</span>
<span class="n">folders_album</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="n">verbose</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">callable</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Album</span><span class="p">:</span>
<span class="sd">"""Get (and create if necessary) a Photos Album by path (pass as list of folders, album name)"""</span>
<span class="n">library</span> <span class="o">=</span> <span class="n">PhotosLibrary</span><span class="p">()</span>
<span class="n">verbose</span> <span class="o">=</span> <span class="n">verbose</span> <span class="ow">or</span> <span class="n">noop</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">folders_album</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
<span class="c1"># have folders</span>
<span class="n">album_name</span> <span class="o">=</span> <span class="n">folders_album</span><span class="o">.</span><span class="n">pop</span><span class="p">()</span>
<span class="n">folder</span> <span class="o">=</span> <span class="n">folder_by_path</span><span class="p">(</span><span class="n">folders_album</span><span class="p">,</span> <span class="n">verbose</span><span class="p">)</span>
<span class="n">album</span> <span class="o">=</span> <span class="n">folder</span><span class="o">.</span><span class="n">album</span><span class="p">(</span><span class="n">album_name</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">album</span><span class="p">:</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Creating album '</span><span class="si">{</span><span class="n">album_name</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
<span class="n">album</span> <span class="o">=</span> <span class="n">folder</span><span class="o">.</span><span class="n">create_album</span><span class="p">(</span><span class="n">album_name</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="c1"># only have album name</span>
<span class="n">album_name</span> <span class="o">=</span> <span class="n">folders_album</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">album</span> <span class="o">=</span> <span class="n">library</span><span class="o">.</span><span class="n">album</span><span class="p">(</span><span class="n">album_name</span><span class="p">,</span> <span class="n">top_level</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">album</span><span class="p">:</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Creating album '</span><span class="si">{</span><span class="n">album_name</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
<span class="n">album</span> <span class="o">=</span> <span class="n">library</span><span class="o">.</span><span class="n">create_album</span><span class="p">(</span><span class="n">album_name</span><span class="p">)</span>
<span class="k">return</span> <span class="n">album</span>
<div class="viewcode-block" id="PhotosAlbumPhotoScript"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotosAlbumPhotoScript">[docs]</a><span class="k">class</span> <span class="nc">PhotosAlbumPhotoScript</span><span class="p">:</span>
<span class="sd">"""Add photoscript.Photo objects to album"""</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">verbose</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">callable</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span> <span class="n">split_folder</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="p">):</span>
<span class="sd">"""Return a PhotosAlbumPhotoScript object, creating the album if necessary</span>
<span class="sd"> Args:</span>
<span class="sd"> name: Name of album</span>
<span class="sd"> verbose: optional callable to print verbose output</span>
<span class="sd"> split_folder: if set, split album name on value of split_folder to create folders if necessary,</span>
<span class="sd"> e.g. if name = 'folder1/folder2/album' and split_folder='/', </span>
<span class="sd"> then folders 'folder1' and 'folder2' will be created and album 'album' will be created in 'folder2';</span>
<span class="sd"> if not set, album 'folder1/folder2/album' will be created</span>
<span class="sd"> """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">verbose</span> <span class="o">=</span> <span class="n">verbose</span> <span class="ow">or</span> <span class="n">noop</span>
<span class="bp">self</span><span class="o">.</span><span class="n">library</span> <span class="o">=</span> <span class="n">PhotosLibrary</span><span class="p">()</span>
<span class="n">folders_album</span> <span class="o">=</span> <span class="n">name</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">split_folder</span><span class="p">)</span> <span class="k">if</span> <span class="n">split_folder</span> <span class="k">else</span> <span class="p">[</span><span class="n">name</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">album</span> <span class="o">=</span> <span class="n">album_by_path</span><span class="p">(</span><span class="n">folders_album</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="n">verbose</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
<span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">photo</span><span class="p">:</span> <span class="n">Photo</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">album</span><span class="o">.</span><span class="n">add</span><span class="p">([</span><span class="n">photo</span><span class="p">])</span>
<span class="bp">self</span><span class="o">.</span><span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Added </span><span class="si">{</span><span class="n">photo</span><span class="o">.</span><span class="n">filename</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2">) to album </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">add_list</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">photo_list</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">Photo</span><span class="p">]):</span>
<span class="k">for</span> <span class="n">photolist</span> <span class="ow">in</span> <span class="n">chunked</span><span class="p">(</span><span class="n">photo_list</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">album</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">photolist</span><span class="p">)</span>
<span class="n">photo_len</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">photo_list</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">verbose</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">"Added </span><span class="si">{</span><span class="n">photo_len</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">pluralize</span><span class="p">(</span><span class="n">photo_len</span><span class="p">,</span> <span class="s1">'photo'</span><span class="p">,</span> <span class="s1">'photos'</span><span class="p">)</span><span class="si">}</span><span class="s2"> to album </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">photos</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">album</span><span class="o">.</span><span class="n">photos</span><span class="p">()</span></div>
</pre></div>
</article>
</div>
<footer>
<div class="related-pages">
</div>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2021, Rhet Turnbull
</div>
Made with <a href="https://www.sphinx-doc.org/">Sphinx</a> and <a class="muted-link" href="https://pradyunsg.me">@pradyunsg</a>'s
<a href="https://github.com/pradyunsg/furo">Furo</a>
</div>
<div class="right-details">
<div class="icons">
</div>
</div>
</div>
</footer>
</div>
<aside class="toc-drawer no-toc">
</aside>
</div>
</div><script data-url_root="../../" id="documentation_options" src="../../_static/documentation_options.js"></script>
<script src="../../_static/jquery.js"></script>
<script src="../../_static/underscore.js"></script>
<script src="../../_static/doctools.js"></script>
<script src="../../_static/scripts/furo.js"></script>
<script src="../../_static/clipboard.min.js"></script>
<script src="../../_static/copybutton.js"></script>
</body>
</html>

View File

@@ -1,52 +1,216 @@
<!doctype html>
<html class="no-js">
<head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../../genindex.html" /><link rel="search" title="Search" href="../../../search.html" />
<!DOCTYPE html>
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.photosdb._photosdb_process_comments - osxphotos 0.50.13 documentation</title>
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../../_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="../../../_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos.photosdb._photosdb_process_comments &#8212; osxphotos 0.47.9 documentation</title>
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../../_static/alabaster.css" />
<script data-url_root="../../../" id="documentation_options" src="../../../_static/documentation_options.js"></script>
<script src="../../../_static/jquery.js"></script>
<script src="../../../_static/underscore.js"></script>
<script src="../../../_static/doctools.js"></script>
<link rel="index" title="Index" href="../../../genindex.html" />
<link rel="search" title="Search" href="../../../search.html" />
<link rel="stylesheet" href="../../../_static/custom.css" type="text/css" />
<style>
body {
--color-code-background: #f8f8f8;
--color-code-foreground: black;
}
@media not print {
body[data-theme="dark"] {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
}
@media (prefers-color-scheme: dark) {
body:not([data-theme="light"]) {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
}
}
}
</style></head>
<body>
<script>
document.body.dataset.theme = localStorage.getItem("theme") || "auto";
</script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-toc" viewBox="0 0 24 24">
<title>Contents</title>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 1024 1024">
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z"/>
</svg>
</symbol>
<symbol id="svg-menu" viewBox="0 0 24 24">
<title>Menu</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-menu">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</symbol>
<symbol id="svg-arrow-right" viewBox="0 0 24 24">
<title>Expand</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24">
<title>Light mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather-sun">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24">
<title>Dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-moon">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
</svg>
</symbol>
<symbol id="svg-sun-half" viewBox="0 0 24 24">
<title>Auto light/dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-shadow">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<circle cx="12" cy="12" r="9" />
<path d="M13 12h5" />
<path d="M13 15h4" />
<path d="M13 18h1" />
<path d="M13 9h4" />
<path d="M13 6h1" />
</svg>
</symbol>
</svg>
<input type="checkbox" class="sidebar-toggle" name="__navigation" id="__navigation">
<input type="checkbox" class="sidebar-toggle" name="__toc" id="__toc">
<label class="overlay sidebar-overlay" for="__navigation">
<div class="visually-hidden">Hide navigation sidebar</div>
</label>
<label class="overlay toc-overlay" for="__toc">
<div class="visually-hidden">Hide table of contents sidebar</div>
</label>
<div class="page">
<header class="mobile-header">
<div class="header-left">
<label class="nav-overlay-icon" for="__navigation">
<div class="visually-hidden">Toggle site navigation sidebar</div>
<i class="icon"><svg><use href="#svg-menu"></use></svg></i>
</label>
</div>
<div class="header-center">
<a href="../../../index.html"><div class="brand">osxphotos 0.50.13 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-header-icon no-toc" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
</label>
</div>
</header>
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../../index.html">
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
</head><body>
<span class="sidebar-brand-text">osxphotos 0.50.13 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
<input type="hidden" name="check_keywords" value="yes">
<input type="hidden" name="area" value="default">
</form>
<div id="searchbox"></div><div class="sidebar-scroll"><div class="sidebar-tree">
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../../overview.html">OSXPhotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../../tutorial.html">OSXPhotos Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../../cli.html">OSXPhotos Command Line Interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../../template_help.html">OSXPhotos Template System</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../../package_overview.html">OSXPhotos Python Package Overview</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../../reference.html">OSXPhotos python API</a></li>
</ul>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
</div>
</div>
<div class="body" role="main">
<h1>Source code for osxphotos.photosdb._photosdb_process_comments</h1><div class="highlight"><pre>
<span></span><span class="sd">&quot;&quot;&quot; PhotosDB method for processing comments and likes on shared photos.</span>
<span class="sd"> Do not import this module directly &quot;&quot;&quot;</span>
</div>
</div>
</aside>
<div class="main">
<div class="content">
<div class="article-container">
<a href="#" class="back-to-top muted-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z"></path>
</svg>
<span>Back to top</span>
</a>
<div class="content-icon-container"><div class="theme-toggle-container theme-toggle-content">
<button class="theme-toggle">
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-content-icon no-toc" for="__toc">
<div class="visually-hidden">Toggle table of contents sidebar</div>
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
</label>
</div>
<article role="main">
<h1>Source code for osxphotos.photosdb._photosdb_process_comments</h1><div class="highlight"><pre>
<span></span><span class="sd">""" PhotosDB method for processing comments and likes on shared photos.</span>
<span class="sd"> Do not import this module directly """</span>
<span class="kn">import</span> <span class="nn">dataclasses</span>
<span class="kn">import</span> <span class="nn">datetime</span>
<span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
<span class="kn">from</span> <span class="nn">.._constants</span> <span class="kn">import</span> <span class="n">_DB_TABLE_NAMES</span><span class="p">,</span> <span class="n">_PHOTOS_4_VERSION</span><span class="p">,</span> <span class="n">TIME_DELTA</span>
<span class="kn">from</span> <span class="nn">..utils</span> <span class="kn">import</span> <span class="n">_open_sql_file</span><span class="p">,</span> <span class="n">normalize_unicode</span>
<span class="kn">from</span> <span class="nn">..utils</span> <span class="kn">import</span> <span class="n">normalize_unicode</span>
<span class="kn">from</span> <span class="nn">..sqlite_utils</span> <span class="kn">import</span> <span class="n">sqlite_open_ro</span>
<span class="k">def</span> <span class="nf">_process_comments</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;load the comments and likes data from the database</span>
<span class="sd">"""load the comments and likes data from the database</span>
<span class="sd"> this is a PhotosDB method that should be imported in</span>
<span class="sd"> the PhotosDB class definition in photosdb.py</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_db_hashed_person_id</span> <span class="o">=</span> <span class="p">{}</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_db_comments_uuid</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db_version</span> <span class="o">&lt;=</span> <span class="n">_PHOTOS_4_VERSION</span><span class="p">:</span>
@@ -57,7 +221,7 @@
<div class="viewcode-block" id="CommentInfo"><a class="viewcode-back" href="../../../reference.html#osxphotos.CommentInfo">[docs]</a><span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">CommentInfo</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Class for shared photo comments&quot;&quot;&quot;</span>
<span class="sd">"""Class for shared photo comments"""</span>
<span class="n">datetime</span><span class="p">:</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span>
<span class="n">user</span><span class="p">:</span> <span class="nb">str</span>
@@ -70,7 +234,7 @@
<div class="viewcode-block" id="LikeInfo"><a class="viewcode-back" href="../../../reference.html#osxphotos.LikeInfo">[docs]</a><span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">LikeInfo</span><span class="p">:</span>
<span class="sd">&quot;&quot;&quot;Class for shared photo likes&quot;&quot;&quot;</span>
<span class="sd">"""Class for shared photo likes"""</span>
<span class="n">datetime</span><span class="p">:</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span>
<span class="n">user</span><span class="p">:</span> <span class="nb">str</span>
@@ -83,25 +247,25 @@
<span class="c1"># The following methods do not get imported into PhotosDB</span>
<span class="c1"># but will get called by _process_comments</span>
<span class="k">def</span> <span class="nf">_process_comments_4</span><span class="p">(</span><span class="n">photosdb</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;process comments and likes info for Photos &lt;= 4</span>
<span class="sd"> photosdb: PhotosDB instance&quot;&quot;&quot;</span>
<span class="sd">"""process comments and likes info for Photos &lt;= 4</span>
<span class="sd"> photosdb: PhotosDB instance"""</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;Not implemented for database version </span><span class="si">{</span><span class="n">photosdb</span><span class="o">.</span><span class="n">_db_version</span><span class="si">}</span><span class="s2">.&quot;</span>
<span class="sa">f</span><span class="s2">"Not implemented for database version </span><span class="si">{</span><span class="n">photosdb</span><span class="o">.</span><span class="n">_db_version</span><span class="si">}</span><span class="s2">."</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">_process_comments_5</span><span class="p">(</span><span class="n">photosdb</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;process comments and likes info for Photos &gt;= 5</span>
<span class="sd"> photosdb: PhotosDB instance&quot;&quot;&quot;</span>
<span class="sd">"""process comments and likes info for Photos &gt;= 5</span>
<span class="sd"> photosdb: PhotosDB instance"""</span>
<span class="n">db</span> <span class="o">=</span> <span class="n">photosdb</span><span class="o">.</span><span class="n">_tmp_db</span>
<span class="n">asset_table</span> <span class="o">=</span> <span class="n">_DB_TABLE_NAMES</span><span class="p">[</span><span class="n">photosdb</span><span class="o">.</span><span class="n">_photos_ver</span><span class="p">][</span><span class="s2">&quot;ASSET&quot;</span><span class="p">]</span>
<span class="n">asset_table</span> <span class="o">=</span> <span class="n">_DB_TABLE_NAMES</span><span class="p">[</span><span class="n">photosdb</span><span class="o">.</span><span class="n">_photos_ver</span><span class="p">][</span><span class="s2">"ASSET"</span><span class="p">]</span>
<span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">cursor</span><span class="p">)</span> <span class="o">=</span> <span class="n">_open_sql_file</span><span class="p">(</span><span class="n">db</span><span class="p">)</span>
<span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">cursor</span><span class="p">)</span> <span class="o">=</span> <span class="n">sqlite_open_ro</span><span class="p">(</span><span class="n">db</span><span class="p">)</span>
<span class="n">results</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="sd">&quot;&quot;&quot;</span>
<span class="sd">"""</span>
<span class="sd"> SELECT DISTINCT</span>
<span class="sd"> ZINVITEEHASHEDPERSONID AS HASHEDPERSONID,</span>
<span class="sd"> ZINVITEEFIRSTNAME AS FIRSTNAME,</span>
@@ -109,7 +273,7 @@
<span class="sd"> ZINVITEEFULLNAME AS FULLNAME</span>
<span class="sd"> FROM ZCLOUDSHAREDALBUMINVITATIONRECORD</span>
<span class="sd"> WHERE HASHEDPERSONID IS NOT NULL</span>
<span class="sd"> AND HASHEDPERSONID != &quot;&quot;</span>
<span class="sd"> AND HASHEDPERSONID != ""</span>
<span class="sd"> AND NOT (FIRSTNAME IS NULL AND LASTNAME IS NULL)</span>
<span class="sd"> UNION</span>
<span class="sd"> SELECT DISTINCT</span>
@@ -119,9 +283,9 @@
<span class="sd"> ZCLOUDOWNERFULLNAME AS FULLNAME</span>
<span class="sd"> FROM ZGENERICALBUM</span>
<span class="sd"> WHERE HASHEDPERSONID IS NOT NULL</span>
<span class="sd"> AND HASHEDPERSONID != &quot;&quot;</span>
<span class="sd"> AND HASHEDPERSONID != ""</span>
<span class="sd"> AND NOT (FIRSTNAME IS NULL AND LASTNAME IS NULL)</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="sd"> """</span>
<span class="p">)</span>
<span class="c1"># order of results</span>
@@ -134,35 +298,35 @@
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">results</span><span class="o">.</span><span class="n">fetchall</span><span class="p">():</span>
<span class="n">person_id</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">photosdb</span><span class="o">.</span><span class="n">_db_hashed_person_id</span><span class="p">[</span><span class="n">person_id</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;first_name&quot;</span><span class="p">:</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">]),</span>
<span class="s2">&quot;last_name&quot;</span><span class="p">:</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">2</span><span class="p">]),</span>
<span class="s2">&quot;full_name&quot;</span><span class="p">:</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">3</span><span class="p">]),</span>
<span class="s2">"first_name"</span><span class="p">:</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">]),</span>
<span class="s2">"last_name"</span><span class="p">:</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">2</span><span class="p">]),</span>
<span class="s2">"full_name"</span><span class="p">:</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">3</span><span class="p">]),</span>
<span class="p">}</span>
<span class="n">results</span> <span class="o">=</span> <span class="n">conn</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">&quot;&quot;&quot;</span>
<span class="sa">f</span><span class="s2">"""</span>
<span class="s2"> SELECT </span>
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZUUID, -- UUID of the photo</span>
<span class="s2"> ZCLOUDSHAREDCOMMENT.ZISLIKE, -- comment is actually a &quot;like&quot;</span>
<span class="s2"> ZCLOUDSHAREDCOMMENT.ZISLIKE, -- comment is actually a "like"</span>
<span class="s2"> ZCLOUDSHAREDCOMMENT.ZCOMMENTDATE, -- date of comment</span>
<span class="s2"> ZCLOUDSHAREDCOMMENT.ZCOMMENTTEXT, -- text of comment</span>
<span class="s2"> ZCLOUDSHAREDCOMMENT.ZCOMMENTERHASHEDPERSONID, -- hashed ID of person who made comment/like</span>
<span class="s2"> ZCLOUDSHAREDCOMMENT.ZISMYCOMMENT -- is my (this user&#39;s) comment</span>
<span class="s2"> ZCLOUDSHAREDCOMMENT.ZISMYCOMMENT -- is my (this user's) comment</span>
<span class="s2"> FROM ZCLOUDSHAREDCOMMENT</span>
<span class="s2"> JOIN </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2"> ON</span>
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.Z_PK = ZCLOUDSHAREDCOMMENT.ZCOMMENTEDASSET</span>
<span class="s2"> OR</span>
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.Z_PK = ZCLOUDSHAREDCOMMENT.ZLIKEDASSET</span>
<span class="s2"> &quot;&quot;&quot;</span>
<span class="s2"> """</span>
<span class="p">)</span>
<span class="c1"># order of results</span>
<span class="c1"># 0: ZGENERICASSET.ZUUID, -- UUID of the photo</span>
<span class="c1"># 1: ZCLOUDSHAREDCOMMENT.ZISLIKE, -- comment is actually a &quot;like&quot;</span>
<span class="c1"># 1: ZCLOUDSHAREDCOMMENT.ZISLIKE, -- comment is actually a "like"</span>
<span class="c1"># 2: ZCLOUDSHAREDCOMMENT.ZCOMMENTDATE, -- date of comment</span>
<span class="c1"># 3: ZCLOUDSHAREDCOMMENT.ZCOMMENTTEXT, -- text of comment</span>
<span class="c1"># 4: ZCLOUDSHAREDCOMMENT.ZCOMMENTERHASHEDPERSONID, -- hashed ID of person who made comment/like</span>
<span class="c1"># 5: ZCLOUDSHAREDCOMMENT.ZISMYCOMMENT -- is my (this user&#39;s) comment</span>
<span class="c1"># 5: ZCLOUDSHAREDCOMMENT.ZISMYCOMMENT -- is my (this user's) comment</span>
<span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">results</span><span class="p">:</span>
@@ -170,7 +334,7 @@
<span class="n">is_like</span> <span class="o">=</span> <span class="nb">bool</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="n">text</span> <span class="o">=</span> <span class="n">normalize_unicode</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="mi">3</span><span class="p">])</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">user_name</span> <span class="o">=</span> <span class="n">photosdb</span><span class="o">.</span><span class="n">_db_hashed_person_id</span><span class="p">[</span><span class="n">row</span><span class="p">[</span><span class="mi">4</span><span class="p">]][</span><span class="s2">&quot;full_name&quot;</span><span class="p">]</span>
<span class="n">user_name</span> <span class="o">=</span> <span class="n">photosdb</span><span class="o">.</span><span class="n">_db_hashed_person_id</span><span class="p">[</span><span class="n">row</span><span class="p">[</span><span class="mi">4</span><span class="p">]][</span><span class="s2">"full_name"</span><span class="p">]</span>
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
<span class="n">user_name</span> <span class="o">=</span> <span class="kc">None</span>
@@ -184,89 +348,62 @@
<span class="k">try</span><span class="p">:</span>
<span class="n">db_comments</span> <span class="o">=</span> <span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="p">[</span><span class="n">uuid</span><span class="p">]</span>
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
<span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="p">[</span><span class="n">uuid</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;likes&quot;</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">&quot;comments&quot;</span><span class="p">:</span> <span class="p">[]}</span>
<span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="p">[</span><span class="n">uuid</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"likes"</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">"comments"</span><span class="p">:</span> <span class="p">[]}</span>
<span class="n">db_comments</span> <span class="o">=</span> <span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="p">[</span><span class="n">uuid</span><span class="p">]</span>
<span class="k">if</span> <span class="n">is_like</span><span class="p">:</span>
<span class="n">db_comments</span><span class="p">[</span><span class="s2">&quot;likes&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">LikeInfo</span><span class="p">(</span><span class="n">dt</span><span class="p">,</span> <span class="n">user_name</span><span class="p">,</span> <span class="n">ismine</span><span class="p">))</span>
<span class="n">db_comments</span><span class="p">[</span><span class="s2">"likes"</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">LikeInfo</span><span class="p">(</span><span class="n">dt</span><span class="p">,</span> <span class="n">user_name</span><span class="p">,</span> <span class="n">ismine</span><span class="p">))</span>
<span class="k">elif</span> <span class="n">text</span><span class="p">:</span>
<span class="n">db_comments</span><span class="p">[</span><span class="s2">&quot;comments&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">CommentInfo</span><span class="p">(</span><span class="n">dt</span><span class="p">,</span> <span class="n">user_name</span><span class="p">,</span> <span class="n">ismine</span><span class="p">,</span> <span class="n">text</span><span class="p">))</span>
<span class="n">db_comments</span><span class="p">[</span><span class="s2">"comments"</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">CommentInfo</span><span class="p">(</span><span class="n">dt</span><span class="p">,</span> <span class="n">user_name</span><span class="p">,</span> <span class="n">ismine</span><span class="p">,</span> <span class="n">text</span><span class="p">))</span>
<span class="c1"># sort results</span>
<span class="k">for</span> <span class="n">uuid</span><span class="p">,</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="k">if</span> <span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;likes&quot;</span><span class="p">]:</span>
<span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;likes&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="o">.</span><span class="n">datetime</span><span class="p">)</span>
<span class="k">if</span> <span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;comments&quot;</span><span class="p">]:</span>
<span class="n">value</span><span class="p">[</span><span class="s2">&quot;comments&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="o">.</span><span class="n">datetime</span><span class="p">)</span>
<span class="k">if</span> <span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"likes"</span><span class="p">]:</span>
<span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"likes"</span><span class="p">]</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="o">.</span><span class="n">datetime</span><span class="p">)</span>
<span class="k">if</span> <span class="n">photosdb</span><span class="o">.</span><span class="n">_db_comments_uuid</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">"comments"</span><span class="p">]:</span>
<span class="n">value</span><span class="p">[</span><span class="s2">"comments"</span><span class="p">]</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="o">.</span><span class="n">datetime</span><span class="p">)</span>
<span class="n">conn</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
</div>
</article>
</div>
<footer>
<div class="related-pages">
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h1 class="logo"><a href="../../../index.html">osxphotos</a></h1>
<h3>Navigation</h3>
<ul>
<li class="toctree-l1"><a class="reference internal" href="../../../overview.html">osxphotos</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../../tutorial.html">Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../../cli.html">osxphotos command line interface (CLI)</a></li>
<li class="toctree-l1"><a class="reference internal" href="../../../reference.html">osxphotos package</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="../../../index.html">Documentation overview</a><ul>
<li><a href="../../index.html">Module code</a><ul>
</ul></li>
</ul></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3 id="searchlabel">Quick search</h3>
<div class="searchformwrapper">
<form class="search" action="../../../search.html" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
<input type="submit" value="Go" />
</form>
</div>
</div>
<script>$('#searchbox').show(0);</script>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2021, Rhet Turnbull
</div>
Made with <a href="https://www.sphinx-doc.org/">Sphinx</a> and <a class="muted-link" href="https://pradyunsg.me">@pradyunsg</a>'s
<a href="https://github.com/pradyunsg/furo">Furo</a>
</div>
<div class="right-details">
<div class="icons">
</div>
</div>
</div>
</div>
<div class="clearer"></div>
</footer>
</div>
<div class="footer">
&copy;2021, Rhet Turnbull.
<aside class="toc-drawer no-toc">
|
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
&amp; <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
</div>
</body>
</aside>
</div>
</div><script data-url_root="../../../" id="documentation_options" src="../../../_static/documentation_options.js"></script>
<script src="../../../_static/jquery.js"></script>
<script src="../../../_static/underscore.js"></script>
<script src="../../../_static/doctools.js"></script>
<script src="../../../_static/scripts/furo.js"></script>
<script src="../../../_static/clipboard.min.js"></script>
<script src="../../../_static/copybutton.js"></script>
</body>
</html>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../../genindex.html" /><link rel="search" title="Search" href="../../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.photosdb.photosdb - osxphotos 0.50.0 documentation</title>
<title>osxphotos.photosdb.photosdb - osxphotos 0.50.13 documentation</title>
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../../index.html"><div class="brand">osxphotos 0.50.0 documentation</div></a>
<a href="../../../index.html"><div class="brand">osxphotos 0.50.13 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../../index.html">
<span class="sidebar-brand-text">osxphotos 0.50.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.50.13 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -210,7 +210,7 @@
<span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">OrderedDict</span>
<span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Iterable</span>
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timedelta</span><span class="p">,</span> <span class="n">timezone</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
<span class="kn">from</span> <span class="nn">unicodedata</span> <span class="kn">import</span> <span class="n">normalize</span>
<span class="kn">import</span> <span class="nn">bitmath</span>
@@ -253,11 +253,10 @@
<span class="kn">from</span> <span class="nn">..phototemplate</span> <span class="kn">import</span> <span class="n">RenderOptions</span>
<span class="kn">from</span> <span class="nn">..queryoptions</span> <span class="kn">import</span> <span class="n">QueryOptions</span>
<span class="kn">from</span> <span class="nn">..rich_utils</span> <span class="kn">import</span> <span class="n">add_rich_markup_tag</span>
<span class="kn">from</span> <span class="nn">..sqlite_utils</span> <span class="kn">import</span> <span class="n">sqlite_open_ro</span><span class="p">,</span> <span class="n">sqlite_db_is_locked</span>
<span class="kn">from</span> <span class="nn">..utils</span> <span class="kn">import</span> <span class="p">(</span>
<span class="n">_check_file_exists</span><span class="p">,</span>
<span class="n">_db_is_locked</span><span class="p">,</span>
<span class="n">_get_os_version</span><span class="p">,</span>
<span class="n">_open_sql_file</span><span class="p">,</span>
<span class="n">get_last_library_path</span><span class="p">,</span>
<span class="n">noop</span><span class="p">,</span>
<span class="n">normalize_unicode</span><span class="p">,</span>
@@ -505,7 +504,7 @@
<span class="c1"># Photos maintains an exclusive lock on the database file while Photos is open</span>
<span class="c1"># photoanalysisd sometimes maintains this lock even after Photos is closed</span>
<span class="c1"># In those cases, make a temp copy of the file for sqlite3 to read</span>
<span class="k">if</span> <span class="n">_db_is_locked</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile</span><span class="p">):</span>
<span class="k">if</span> <span class="n">sqlite_db_is_locked</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile</span><span class="p">):</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Database locked, creating temporary copy."</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_copy_db_file</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile</span><span class="p">)</span>
@@ -521,7 +520,7 @@
<span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span> <span class="o">=</span> <span class="n">dbfile</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Processing database </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">_filepath</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span><span class="p">)</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="c1"># if database is exclusively locked, make a copy of it and use the copy</span>
<span class="k">if</span> <span class="n">_db_is_locked</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span><span class="p">):</span>
<span class="k">if</span> <span class="n">sqlite_db_is_locked</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span><span class="p">):</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Database locked, creating temporary copy."</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_copy_db_file</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbfile_actual</span><span class="p">)</span>
@@ -774,7 +773,7 @@
<span class="sd"> Returns:</span>
<span class="sd"> tuple of (connection, cursor) to sqlite3 database</span>
<span class="sd"> """</span>
<span class="k">return</span> <span class="n">_open_sql_file</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span><span class="p">)</span></div>
<span class="k">return</span> <span class="n">sqlite_open_ro</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span><span class="p">)</span></div>
<span class="k">def</span> <span class="nf">_copy_db_file</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">fname</span><span class="p">):</span>
<span class="sd">"""copies the sqlite database file to a temp file"""</span>
@@ -838,7 +837,7 @@
<span class="bp">self</span><span class="o">.</span><span class="n">_photos_ver</span> <span class="o">=</span> <span class="mi">4</span> <span class="c1"># only used in Photos 5+</span>
<span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="n">_open_sql_file</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span><span class="p">)</span>
<span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="n">sqlite_open_ro</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span><span class="p">)</span>
<span class="c1"># get info to associate persons with photos</span>
<span class="c1"># then get detected faces in each photo and link to persons</span>
@@ -1789,7 +1788,7 @@
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="sa">f</span><span class="s2">"_process_database5"</span><span class="p">)</span>
<span class="n">verbose</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_verbose</span>
<span class="n">verbose</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Processing database."</span><span class="p">)</span>
<span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="n">_open_sql_file</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span><span class="p">)</span>
<span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span> <span class="o">=</span> <span class="n">sqlite_open_ro</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span><span class="p">)</span>
<span class="c1"># some of the tables/columns have different names in different versions of Photos</span>
<span class="n">photos_ver</span> <span class="o">=</span> <span class="n">get_db_model_version</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_tmp_db</span><span class="p">)</span>
@@ -3054,16 +3053,16 @@
<div class="viewcode-block" id="PhotosDB.photos"><a class="viewcode-back" href="../../../reference.html#osxphotos.PhotosDB.photos">[docs]</a> <span class="k">def</span> <span class="nf">photos</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span>
<span class="n">keywords</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">uuid</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">persons</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">albums</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">images</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">movies</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">from_date</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">to_date</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">intrash</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
<span class="p">):</span>
<span class="n">keywords</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">uuid</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">persons</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">albums</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">images</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
<span class="n">movies</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">True</span><span class="p">,</span>
<span class="n">from_date</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">to_date</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">datetime</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">intrash</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">PhotoInfo</span><span class="p">]:</span>
<span class="sd">"""Return a list of PhotoInfo objects</span>
<span class="sd"> If called with no args, returns the entire database of photos</span>
<span class="sd"> If called with args, returns photos matching the args (e.g. keywords, persons, etc.)</span>
@@ -3483,6 +3482,8 @@
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">is_reference</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">isreference</span><span class="p">]</span>
<span class="k">elif</span> <span class="n">options</span><span class="o">.</span><span class="n">not_reference</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">p</span><span class="o">.</span><span class="n">isreference</span><span class="p">]</span>
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">in_album</span><span class="p">:</span>
<span class="n">photos</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">photos</span> <span class="k">if</span> <span class="n">p</span><span class="o">.</span><span class="n">albums</span><span class="p">]</span>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.phototemplate - osxphotos 0.50.0 documentation</title>
<title>osxphotos.phototemplate - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.50.0 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.50.0 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -216,7 +216,7 @@
<span class="kn">from</span> <span class="nn">.exiftool</span> <span class="kn">import</span> <span class="n">ExifToolCaching</span>
<span class="kn">from</span> <span class="nn">.path_utils</span> <span class="kn">import</span> <span class="n">sanitize_dirname</span><span class="p">,</span> <span class="n">sanitize_filename</span><span class="p">,</span> <span class="n">sanitize_pathpart</span>
<span class="kn">from</span> <span class="nn">.text_detection</span> <span class="kn">import</span> <span class="n">detect_text</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">expand_and_validate_filepath</span><span class="p">,</span> <span class="n">load_function</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">expand_and_validate_filepath</span><span class="p">,</span> <span class="n">load_function</span><span class="p">,</span> <span class="n">uuid_to_shortuuid</span>
<span class="n">__all__</span> <span class="o">=</span> <span class="p">[</span>
<span class="s2">"RenderOptions"</span><span class="p">,</span>
@@ -337,6 +337,8 @@
<span class="s2">"</span><span class="si">{exif.lens_model}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"Lens model from original photo's EXIF information as imported by Photos, e.g. 'iPhone 6s back camera 4.15mm f/2.2'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{moment}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"The moment title of the photo"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{uuid}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{shortuuid}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"A shorter representation of photo's internal universally unique identifier (UUID) for the photo, "</span>
<span class="o">+</span> <span class="s2">"a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{id}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"A unique number for the photo based on its primary key in the Photos database. "</span>
<span class="o">+</span> <span class="s2">"A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. "</span>
<span class="o">+</span> <span class="s2">"May be formatted using a python string format code. "</span>
@@ -375,7 +377,8 @@
<span class="s2">"</span><span class="si">{newline}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">"A newline: '\n'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{lf}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">"A line feed: '\n', alias for </span><span class="si">{newline}</span><span class="s2">"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{cr}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">"A carriage return: '\r'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{crlf}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">"a carriage return + line feed: '\r\n'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{crlf}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">"A carriage return + line feed: '\r\n'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{tab}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">":A tab: '\t'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{osxphotos_version}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">f</span><span class="s2">"The osxphotos version, e.g. '</span><span class="si">{</span><span class="n">__version__</span><span class="si">}</span><span class="s2">'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{osxphotos_cmd_line}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"The full command line used to run osxphotos"</span><span class="p">,</span>
<span class="p">}</span>
@@ -449,10 +452,24 @@
<span class="s2">"rsort"</span><span class="p">:</span> <span class="s2">"Sort list of values in reverse order, e.g. ['a', 'b', 'c'] =&gt; ['c', 'b', 'a']."</span><span class="p">,</span>
<span class="s2">"reverse"</span><span class="p">:</span> <span class="s2">"Reverse order of values, e.g. ['a', 'b', 'c'] =&gt; ['c', 'b', 'a']."</span><span class="p">,</span>
<span class="s2">"uniq"</span><span class="p">:</span> <span class="s2">"Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] =&gt; ['a', 'b', 'c']."</span><span class="p">,</span>
<span class="s2">"join(x)"</span><span class="p">:</span> <span class="s2">"Join list of values with delimiter x, e.g. join(:): ['a', 'b', 'c'] =&gt; 'a:b:c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters."</span><span class="p">,</span>
<span class="s2">"join(x)"</span><span class="p">:</span> <span class="s2">"Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] =&gt; 'a,b,c'; "</span>
<span class="o">+</span> <span class="s2">"the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters."</span>
<span class="o">+</span> <span class="s2">"May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. "</span>
<span class="o">+</span> <span class="s2">"e.g. join(): ['a', 'b', 'c'] =&gt; 'abc'."</span><span class="p">,</span>
<span class="s2">"append(x)"</span><span class="p">:</span> <span class="s2">"Append x to list of values, e.g. append(d): ['a', 'b', 'c'] =&gt; ['a', 'b', 'c', 'd']."</span><span class="p">,</span>
<span class="s2">"prepend(x)"</span><span class="p">:</span> <span class="s2">"Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] =&gt; ['d', 'a', 'b', 'c']."</span><span class="p">,</span>
<span class="s2">"remove(x)"</span><span class="p">:</span> <span class="s2">"Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] =&gt; ['a', 'c']."</span><span class="p">,</span>
<span class="s2">"slice(start:stop:step)"</span><span class="p">:</span> <span class="s2">"Slice list using same semantics as Python's list slicing, "</span>
<span class="o">+</span> <span class="s2">"e.g. slice(1:3): ['a', 'b', 'c', 'd'] =&gt; ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] =&gt; ['b', 'd']; "</span>
<span class="o">+</span> <span class="s2">"slice(1:): ['a', 'b', 'c', 'd'] =&gt; ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] =&gt; ['a', 'b', 'c']; "</span>
<span class="o">+</span> <span class="s2">"slice(::-1): ['a', 'b', 'c', 'd'] =&gt; ['d', 'c', 'b', 'a']. See also sslice()."</span><span class="p">,</span>
<span class="s2">"sslice(start:stop:step)"</span><span class="p">:</span> <span class="s2">"[s(tring) slice] Slice values in a list using same semantics as Python's string slicing, "</span>
<span class="o">+</span> <span class="s2">"e.g. sslice(1:3):'abcd =&gt; 'bc'; sslice(1:4:2): 'abcd' =&gt; 'bd', etc. See also slice()."</span><span class="p">,</span>
<span class="s2">"filter(x)"</span><span class="p">:</span> <span class="s2">"Filter list of values using predicate x; for example, `{folder_album|filter(contains Events)}` returns only folders/albums containing the word 'Events' in their path."</span><span class="p">,</span>
<span class="s2">"int"</span><span class="p">:</span> <span class="s2">"Convert values in list to integer, e.g. 1.0 =&gt; 1. If value cannot be converted to integer, remove value from list. "</span>
<span class="o">+</span> <span class="s2">"['1.1', 'x'] =&gt; ['1']. See also float."</span><span class="p">,</span>
<span class="s2">"float"</span><span class="p">:</span> <span class="s2">"Convert values in list to floating point number, e.g. 1 =&gt; 1.0. If value cannot be converted to float, remove value from list. "</span>
<span class="o">+</span> <span class="s2">"['1', 'x'] =&gt; ['1.0']. See also int."</span><span class="p">,</span>
<span class="p">}</span>
<span class="c1"># Just the substitutions without the braces</span>
@@ -492,6 +509,7 @@
<span class="s2">"lf"</span><span class="p">:</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span>
<span class="s2">"cr"</span><span class="p">:</span> <span class="s2">"</span><span class="se">\r</span><span class="s2">"</span><span class="p">,</span>
<span class="s2">"crlf"</span><span class="p">:</span> <span class="s2">"</span><span class="se">\r\n</span><span class="s2">"</span><span class="p">,</span>
<span class="s2">"tab"</span><span class="p">:</span> <span class="s2">"</span><span class="se">\t</span><span class="s2">"</span><span class="p">,</span>
<span class="p">}</span>
@@ -514,6 +532,7 @@
<span class="sd"> dest_path: set to the destination path of the photo (for use by {function} template), only valid with --filename</span>
<span class="sd"> filepath: set to value for filepath of the exported photo if you want to evaluate {filepath} template</span>
<span class="sd"> quote: quote path templates for execution in the shell</span>
<span class="sd"> caller: which command is calling the template (e.g. 'export')</span>
<span class="sd"> """</span>
<span class="n">none_str</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">"_"</span>
@@ -528,6 +547,7 @@
<span class="n">dest_path</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">filepath</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">quote</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">caller</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">"export"</span>
<span class="k">class</span> <span class="nc">PhotoTemplateParser</span><span class="p">:</span>
@@ -850,16 +870,18 @@
<span class="n">vals</span> <span class="o">=</span> <span class="n">string_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="n">c</span><span class="p">))</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s2">"=="</span><span class="p">:</span>
<span class="n">match</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">vals</span><span class="p">)</span> <span class="o">==</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">conditional_value</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">):</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">)</span>
<span class="k">else</span> <span class="p">[]</span>
<span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s2">"!="</span><span class="p">:</span>
<span class="n">match</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">vals</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">conditional_value</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">):</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">)</span>
<span class="k">else</span> <span class="p">[]</span>
<span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s2">"&lt;"</span><span class="p">:</span>
<span class="n">vals</span> <span class="o">=</span> <span class="n">comparison_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span> <span class="o">&lt;</span> <span class="n">c</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s2">"&lt;="</span><span class="p">:</span>
@@ -973,7 +995,9 @@
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
<span class="s2">"SyntaxError: filename and function must not be null with {function::filename.py:function_name}"</span>
<span class="p">)</span>
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_function</span><span class="p">(</span><span class="n">subfield</span><span class="p">,</span> <span class="n">field_arg</span><span class="p">)</span>
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_function</span><span class="p">(</span>
<span class="n">subfield</span><span class="p">,</span> <span class="n">field_arg</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">caller</span>
<span class="p">)</span>
<span class="k">elif</span> <span class="n">field</span> <span class="ow">in</span> <span class="n">MULTI_VALUE_SUBSTITUTIONS</span> <span class="ow">or</span> <span class="n">field</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"photo"</span><span class="p">):</span>
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_multi</span><span class="p">(</span>
<span class="n">field</span><span class="p">,</span> <span class="n">subfield</span><span class="p">,</span> <span class="n">path_sep</span><span class="o">=</span><span class="n">field_arg</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">default</span>
@@ -1274,6 +1298,8 @@
<span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">moment_info</span><span class="o">.</span><span class="n">title</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">moment_info</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"uuid"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"shortuuid"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">uuid_to_shortuuid</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span><span class="p">)</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">uuid</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">elif</span> <span class="n">field</span> <span class="o">==</span> <span class="s2">"id"</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">format_str_value</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">"pk"</span><span class="p">],</span> <span class="n">subfield</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">field</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"album_seq"</span><span class="p">)</span> <span class="ow">or</span> <span class="n">field</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"folder_album_seq"</span><span class="p">):</span>
@@ -1356,10 +1382,12 @@
<span class="s2">"split"</span><span class="p">,</span>
<span class="s2">"chop"</span><span class="p">,</span>
<span class="s2">"chomp"</span><span class="p">,</span>
<span class="s2">"join"</span><span class="p">,</span>
<span class="s2">"append"</span><span class="p">,</span>
<span class="s2">"prepend"</span><span class="p">,</span>
<span class="s2">"remove"</span><span class="p">,</span>
<span class="s2">"slice"</span><span class="p">,</span>
<span class="s2">"sslice"</span><span class="p">,</span>
<span class="s2">"filter"</span><span class="p">,</span>
<span class="p">]</span> <span class="ow">and</span> <span class="p">(</span><span class="n">args</span> <span class="ow">is</span> <span class="kc">None</span> <span class="ow">or</span> <span class="ow">not</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)):</span>
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">filter_</span><span class="si">}</span><span class="s2"> requires arguments"</span><span class="p">)</span>
@@ -1430,7 +1458,7 @@
<span class="n">value</span> <span class="o">=</span> <span class="n">temp_values</span>
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"join"</span><span class="p">:</span>
<span class="c1"># join list of values with delimiter</span>
<span class="n">delim</span> <span class="o">=</span> <span class="n">args</span>
<span class="n">delim</span> <span class="o">=</span> <span class="n">args</span> <span class="ow">or</span> <span class="s2">""</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">delim</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">values</span><span class="p">)]</span>
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"append"</span><span class="p">:</span>
<span class="c1"># append value to list</span>
@@ -1441,12 +1469,79 @@
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"remove"</span><span class="p">:</span>
<span class="c1"># remove value from list</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span> <span class="k">if</span> <span class="n">v</span> <span class="o">!=</span> <span class="n">args</span><span class="p">]</span>
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"slice"</span><span class="p">:</span>
<span class="c1"># slice list of values</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">values</span><span class="p">[</span><span class="n">create_slice</span><span class="p">(</span><span class="n">args</span><span class="p">)]</span>
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"sslice"</span><span class="p">:</span>
<span class="c1"># slice each value in a list</span>
<span class="n">slice_</span> <span class="o">=</span> <span class="n">create_slice</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span><span class="p">[</span><span class="n">slice_</span><span class="p">]</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">]</span>
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"filter"</span><span class="p">:</span>
<span class="c1"># filter values based on a predicate</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">filter_predicate</span><span class="p">(</span><span class="n">v</span><span class="p">,</span> <span class="n">args</span><span class="p">)]</span>
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"int"</span><span class="p">:</span>
<span class="c1"># convert value to integer</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">values_to_int</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">filter_</span> <span class="o">==</span> <span class="s2">"float"</span><span class="p">:</span>
<span class="c1"># convert value to float</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">values_to_float</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">filter_</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"function:"</span><span class="p">):</span>
<span class="n">value</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_filter_function</span><span class="p">(</span><span class="n">filter_</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">values</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">value</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">return</span> <span class="n">value</span></div>
<div class="viewcode-block" id="PhotoTemplate.filter_predicate"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.filter_predicate">[docs]</a> <span class="k">def</span> <span class="nf">filter_predicate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">args</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
<span class="sd">"""Return True if value passes predicate"""</span>
<span class="c1"># extract function name and arguments</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">args</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="s2">"Filter predicate requires arguments"</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">if</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"not"</span><span class="p">:</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="k">return</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">filter_predicate</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="s2">" "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">args</span><span class="p">))</span>
<span class="n">predicate</span> <span class="o">=</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">conditional_value</span> <span class="o">=</span> <span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"|"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">comparison_test</span><span class="p">(</span><span class="n">test_function</span><span class="p">):</span>
<span class="sd">"""Perform numerical comparisons using test_function"""</span>
<span class="c1"># returns True if any of the values match the condition</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
<span class="nb">bool</span><span class="p">(</span><span class="n">test_function</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">value</span><span class="p">),</span> <span class="nb">float</span><span class="p">(</span><span class="n">c</span><span class="p">)))</span>
<span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span>
<span class="p">)</span>
<span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">"comparison operators may only be used with values that can be converted to numbers: </span><span class="si">{</span><span class="n">vals</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">conditional_value</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">if</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"contains"</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">c</span> <span class="ow">in</span> <span class="n">value</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"endswith"</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">value</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"matches"</span><span class="p">,</span> <span class="s2">"=="</span><span class="p">]:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">value</span> <span class="o">==</span> <span class="n">c</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"startswith"</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">value</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"!="</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">value</span> <span class="o">!=</span> <span class="n">c</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"&lt;"</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="n">comparison_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span> <span class="o">&lt;</span> <span class="n">c</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"&lt;="</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="n">comparison_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span> <span class="o">&lt;=</span> <span class="n">c</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"&gt;"</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="n">comparison_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span> <span class="o">&gt;</span> <span class="n">c</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"&gt;="</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="n">comparison_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span> <span class="o">&gt;=</span> <span class="n">c</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Invalid predicate: </span><span class="si">{</span><span class="n">predicate</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">predicate_is_true</span></div>
<div class="viewcode-block" id="PhotoTemplate.get_template_value_multi"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_template_value_multi">[docs]</a> <span class="k">def</span> <span class="nf">get_template_value_multi</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">,</span> <span class="n">subfield</span><span class="p">,</span> <span class="n">path_sep</span><span class="p">,</span> <span class="n">default</span><span class="p">):</span>
<span class="sd">"""lookup value for template field (multi-value template substitutions)</span>
@@ -1634,10 +1729,17 @@
<div class="viewcode-block" id="PhotoTemplate.get_template_value_function"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_template_value_function">[docs]</a> <span class="k">def</span> <span class="nf">get_template_value_function</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span>
<span class="n">subfield</span><span class="p">,</span>
<span class="n">field_arg</span><span class="p">,</span>
<span class="n">subfield</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">field_arg</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
<span class="n">caller</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="p">):</span>
<span class="sd">"""Get template value from external function"""</span>
<span class="sd">"""Get template value from external function</span>
<span class="sd"> Args:</span>
<span class="sd"> subfield: the filename and function name in for filename.py::function</span>
<span class="sd"> field_arg: the argument to pass to the function</span>
<span class="sd"> caller: the calling source of the template ('export' or 'import')</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="s2">"::"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">subfield</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
@@ -1656,8 +1758,17 @@
<span class="c1"># if no uuid, then template is being validated but not actually run</span>
<span class="c1"># so don't run the function</span>
<span class="n">values</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">elif</span> <span class="n">caller</span> <span class="o">==</span> <span class="s2">"export"</span><span class="p">:</span>
<span class="c1"># function signature is:</span>
<span class="c1"># def example(photo: PhotoInfo, options: ExportOptions, args: Optional[str] = None, **kwargs) -&gt; Union[List, str]:</span>
<span class="n">values</span> <span class="o">=</span> <span class="n">template_func</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="n">field_arg</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">caller</span> <span class="o">==</span> <span class="s2">"import"</span><span class="p">:</span>
<span class="c1"># function signature is:</span>
<span class="c1"># def example(filepath: pathlib.Path, args: Optional[str] = None, **kwargs) -&gt; Union[List, str]:</span>
<span class="c1"># the PhotoInfoFromFile class used by import sets `path` to the path of the file being imported</span>
<span class="n">values</span> <span class="o">=</span> <span class="n">template_func</span><span class="p">(</span><span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">path</span><span class="p">),</span> <span class="n">args</span><span class="o">=</span><span class="n">field_arg</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Unhandled caller: </span><span class="si">{</span><span class="n">caller</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">values</span><span class="p">,</span> <span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="nb">list</span><span class="p">)):</span>
<span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
@@ -1804,20 +1915,18 @@
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">return</span> <span class="n">shlex</span><span class="o">.</span><span class="n">quote</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">if</span> <span class="n">quote</span> <span class="k">else</span> <span class="n">value</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">2</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Illegal value for path template: </span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">attribute</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">val</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">attribute</span><span class="p">)</span>
<span class="n">val_str</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
<span class="k">if</span> <span class="n">quote</span><span class="p">:</span>
<span class="n">val_str</span> <span class="o">=</span> <span class="n">shlex</span><span class="o">.</span><span class="n">quote</span><span class="p">(</span><span class="n">val_str</span><span class="p">)</span>
<span class="k">return</span> <span class="n">val_str</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"Illegal value for path template: </span><span class="si">{attribute}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">for</span> <span class="n">attribute</span> <span class="ow">in</span> <span class="n">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">:]:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">val</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">attribute</span><span class="p">)</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">AttributeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Illegal value for filepath template: </span><span class="si">{</span><span class="n">attribute</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
<span class="n">val_str</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
<span class="k">if</span> <span class="n">quote</span><span class="p">:</span>
<span class="n">val_str</span> <span class="o">=</span> <span class="n">shlex</span><span class="o">.</span><span class="n">quote</span><span class="p">(</span><span class="n">val_str</span><span class="p">)</span>
<span class="k">return</span> <span class="n">val_str</span>
<span class="k">def</span> <span class="nf">format_str_value</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">format_str</span><span class="p">):</span>
@@ -1865,6 +1974,46 @@
<span class="c1"># so the first time this gets called is slow but repeated accesses are fast</span>
<span class="n">detected_text</span> <span class="o">=</span> <span class="n">photo</span><span class="o">.</span><span class="n">_detected_text</span><span class="p">()</span>
<span class="k">return</span> <span class="p">[</span><span class="n">text</span> <span class="k">for</span> <span class="n">text</span><span class="p">,</span> <span class="n">conf</span> <span class="ow">in</span> <span class="n">detected_text</span> <span class="k">if</span> <span class="n">conf</span> <span class="o">&gt;=</span> <span class="n">confidence</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">create_slice</span><span class="p">(</span><span class="n">args</span><span class="p">):</span>
<span class="sd">"""Create a slice object from a string of args in form "start:end:step" """</span>
<span class="n">slice_args</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">":"</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">slice_args</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
<span class="n">start</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">slice_args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">or</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">end</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">step</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">elif</span> <span class="nb">len</span><span class="p">(</span><span class="n">slice_args</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
<span class="n">start</span><span class="p">,</span> <span class="n">end</span> <span class="o">=</span> <span class="n">slice_args</span>
<span class="n">start</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">start</span><span class="p">)</span> <span class="k">if</span> <span class="n">start</span> <span class="o">!=</span> <span class="s2">""</span> <span class="k">else</span> <span class="kc">None</span>
<span class="n">end</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">end</span><span class="p">)</span> <span class="k">if</span> <span class="n">end</span> <span class="o">!=</span> <span class="s2">""</span> <span class="k">else</span> <span class="kc">None</span>
<span class="n">step</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">elif</span> <span class="nb">len</span><span class="p">(</span><span class="n">slice_args</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
<span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">,</span> <span class="n">step</span> <span class="o">=</span> <span class="n">slice_args</span>
<span class="n">start</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">start</span><span class="p">)</span> <span class="k">if</span> <span class="n">start</span> <span class="o">!=</span> <span class="s2">""</span> <span class="k">else</span> <span class="kc">None</span>
<span class="n">end</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">end</span><span class="p">)</span> <span class="k">if</span> <span class="n">end</span> <span class="o">!=</span> <span class="s2">""</span> <span class="k">else</span> <span class="kc">None</span>
<span class="n">step</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">step</span><span class="p">)</span> <span class="k">if</span> <span class="n">step</span> <span class="o">!=</span> <span class="s2">""</span> <span class="k">else</span> <span class="kc">None</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Invalid slice: </span><span class="si">{</span><span class="n">args</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">slice</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">,</span> <span class="n">step</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">values_to_int</span><span class="p">(</span><span class="n">values</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="sd">"""Convert a list of strings to str representation of ints, if possible, otherwise strip values from list"""</span>
<span class="n">int_values</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">:</span>
<span class="k">with</span> <span class="n">suppress</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">):</span>
<span class="n">int_values</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">v</span><span class="p">))))</span>
<span class="k">return</span> <span class="n">int_values</span>
<span class="k">def</span> <span class="nf">values_to_float</span><span class="p">(</span><span class="n">values</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="sd">"""Convert a list of strings to str representation of float, if possible, otherwise strip values from list"""</span>
<span class="n">float_values</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">:</span>
<span class="k">with</span> <span class="n">suppress</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">):</span>
<span class="n">float_values</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">v</span><span class="p">)))</span>
<span class="k">return</span> <span class="n">float_values</span>
</pre></div>
</article>
</div>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.queryoptions - osxphotos 0.48.7 documentation</title>
<title>osxphotos.queryoptions - osxphotos 0.50.13 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.48.7 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.50.13 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.48.7 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.50.13 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -266,6 +266,7 @@
<span class="sd"> not_missing: search for non-missing photos</span>
<span class="sd"> not_panorama: search for non-panorama photos</span>
<span class="sd"> not_portrait: search for non-portrait photos</span>
<span class="sd"> not_reference: search for photos not stored by reference (that is, they are managed by Photos)</span>
<span class="sd"> not_screenshot: search for non-screenshot photos</span>
<span class="sd"> not_selfie: search for non-selfie photos</span>
<span class="sd"> not_shared: search for non-shared photos</span>
@@ -347,6 +348,7 @@
<span class="n">not_missing</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_panorama</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_portrait</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_reference</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_screenshot</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_selfie</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">not_shared</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>

View File

@@ -55,10 +55,15 @@ Valid filters are:
* ``rsort``\ : Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
* ``reverse``\ : Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
* ``uniq``\ : Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].
* `join(x)`: Join list of values with delimiter x, e.g. join(:): ['a', 'b', 'c'] => 'a:b:c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.
* `join(x)`: Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.
* `append(x)`: Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].
* `prepend(x)`: Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].
* `remove(x)`: Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].
* `slice(start:stop:step)`: Slice list using same semantics as Python's list slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().
* `sslice(start:stop:step)`: [s(tring) slice] Slice values in a list using same semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().
* ``filter(x)``\ : Filter list of values using predicate x; for example, ``{folder_album|filter(contains Events)}`` returns only folders/albums containing the word 'Events' in their path.
* ``int``\ : Convert values in list to integer, e.g. 1.0 => 1. If value cannot be converted to integer, remove value from list. ['1.1', 'x'] => ['1']. See also float.
* ``float``\ : Convert values in list to floating point number, e.g. 1 => 1.0. If value cannot be converted to float, remove value from list. ['1', 'x'] => ['1.0']. See also int.
e.g. if Photo keywords are ``["FOO","bar"]``\ :
@@ -307,6 +312,8 @@ Template Substitutions
- The moment title of the photo
* - {uuid}
- Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'
* - {shortuuid}
- A shorter representation of photo's internal universally unique identifier (UUID) for the photo, a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'
* - {id}
- A unique number for the photo based on its primary key in the Photos database. A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{id:05d}' which results in 00001, 00002, 00003...etc.
* - {album_seq}
@@ -340,9 +347,11 @@ Template Substitutions
* - {cr}
- A carriage return: '\r'
* - {crlf}
- a carriage return + line feed: '\r\n'
- A carriage return + line feed: '\r\n'
* - {tab}
- :A tab: '\t'
* - {osxphotos_version}
- The osxphotos version, e.g. '0.50.1'
- The osxphotos version, e.g. '0.51.4'
* - {osxphotos_cmd_line}
- The full command line used to run osxphotos
* - {album}

View File

@@ -73,6 +73,22 @@ the exported files would be:
/path/to/export/Vacation/IMG_1234.JPG
If your photos are organized in folders and albums in Photos you can preserve this structure on export by using the ``{folder_album}`` template field with the ``--directory`` option. For example, if you have a photo in the album ``Vacation`` which is in the ``Travel`` folder, the following command would export the photo to the ``Travel/Vacation`` directory:
``osxphotos export /path/to/export --directory "{folder_album}"``
Photos can belong to more than one album. In this case, the template field ``{folder_album}`` will expand to all the album names that the photo belongs to. For example, if a photo belongs to the albums ``Vacation`` and ``Travel``\ , the template field ``{folder_album}`` would expand to ``Vacation``\ , ``Travel``. If the photo belongs to no albums, the template field ``{folder_album}`` would expand to "_" (the default value).
All template fields including ``{folder_album}`` can be further filtered using a number of different filters. To convert all directory names to lower case for example, use the ``lower`` filter:
``osxphotos export /path/to/export --directory "{folder_album|lower}"``
If all your photos were organized into various albums under a folder named ``Events`` but some where also included in other top-level albums and you wanted to export only the ``Events`` folder, you could use the ``filter`` option to filter out the other top-level albums by selecting only those folder/album paths that start with ``Events``\ :
``osxphotos export /path/to/export --directory "{folder_album|filter(startswith Events)}"``
You can learn more about the other filters using ``osxphotos help export``.
Specify exported filename
-------------------------
@@ -275,6 +291,14 @@ You can use the ``--report`` option to create a report, in comma-separated value
``osxphotos export /path/to/export --report export.csv``
You can also create reports in JSON or SQLite format by changing the extension of the report filename. For example, to create a JSON report:
``osxphotos export /path/to/export --report export.json``
And to create a SQLite report:
``osxphotos export /path/to/export --report export.sqlite``
Exporting only certain photos
-----------------------------

View File

@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.50.1',
VERSION: '0.51.4',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Template System" href="template_help.html" /><link rel="prev" title="OSXPhotos Tutorial" href="tutorial.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.50.1 documentation</title>
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -336,6 +336,11 @@ library specified by db to the database specified by DB2</p>
<span class="sig-name descname"><span class="pre">--deleted-only</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-dump-deleted-only" title="Permalink to this definition">#</a></dt>
<dd><p>Include only photos from the Recently Deleted folder.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-dump-print">
<span class="sig-name descname"><span class="pre">--print</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-dump-print" title="Permalink to this definition">#</a></dt>
<dd><p>Render TEMPLATE string for each photo queried and print to stdout. TEMPLATE is an osxphotos template string. This may be useful for creating custom reports, etc. If print TEMPLATE is provided, regular output is suppressed and only the rendered TEMPLATE values are printed. May be repeated to print multiple template strings.</p>
</dd></dl>
<p class="rubric">Arguments</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-dump-arg-PHOTOS_LIBRARY">
@@ -817,6 +822,11 @@ to modify this behavior.</p>
<dd><p>Search for photos that were imported as referenced files (not copied into Photos library).</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-not-reference">
<span class="sig-name descname"><span class="pre">--not-reference</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-not-reference" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that are not references, that is, they were copied into the Photos library and are managed by Photos.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-in-album">
<span class="sig-name descname"><span class="pre">--in-album</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-in-album" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that are in one or more albums.</p>
@@ -1049,6 +1059,11 @@ to modify this behavior.</p>
<dd><p>Merge any persons found in the original file with persons used for exiftool and sidecar.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-favorite-rating">
<span class="sig-name descname"><span class="pre">--favorite-rating</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-favorite-rating" title="Permalink to this definition">#</a></dt>
<dd><p>When used with exiftool or sidecar, set XMP:Rating=5 for photos marked as Favorite and XMP:Rating=0 for non-Favorites. If not specified, XMP:Rating is not set.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-ignore-date-modified">
<span class="sig-name descname"><span class="pre">--ignore-date-modified</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-export-ignore-date-modified" title="Permalink to this definition">#</a></dt>
<dd><p>If used with exiftool or sidecar, will ignore the photo modification date and set EXIF:ModifyDate to EXIF:DateTimeOriginal; this is consistent with how Photos handles the EXIF:ModifyDate tag.</p>
@@ -1154,6 +1169,11 @@ to modify this behavior.</p>
<dd><p>Cleanup export directory by deleting any files which were not included in this export set. For example, photos which had previously been exported and were subsequently deleted in Photos. WARNING: cleanup will delete <em>any</em> files in the export directory that were not exported by osxphotos, for example, your own scripts or other files. Be sure this is what you intend before using cleanup. Use dry-run with cleanup first if youre not certain.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-keep">
<span class="sig-name descname"><span class="pre">--keep</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;KEEP_PATH&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-keep" title="Permalink to this definition">#</a></dt>
<dd><p>When used with cleanup, prevents file or directory KEEP_PATH from being deleted when cleanup is run. Use this if there are files in the export directory that you dont want to be deleted when cleanup is run. KEEP_PATH may be a file path, e.g. /Volumes/Photos/keep.jpg, or a file path and wild card, e.g. /Volumes/Photos/<em>.txt, or a directory, e.g. /Volumes/Photos/KeepMe. KEEP_PATH may be an absolute path or a relative path. If it is relative, it must be relative to the export destination. For example if export destination is `/Volumes/Photos` and you want to keep all `.txt` files, you can specify `keep “/Volumes/Photos/</em>.txt”` or <cite>keep “*.txt”</cite>. If wild card is used, KEEP_PATH must be enclosed in quotes to prevent the shell from expanding the wildcard, e.g. <cite>keep “/Volumes/Photos/*.txt”</cite>. If KEEP_PATH is a directory, all files and directories contained in KEEP_PATH will be kept. keep may be repeated to keep additional files/directories.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-add-exported-to-album">
<span class="sig-name descname"><span class="pre">--add-exported-to-album</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;ALBUM&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-add-exported-to-album" title="Permalink to this definition">#</a></dt>
<dd><p>Add all exported photos to album ALBUM in Photos. Album ALBUM will be created if it doesnt exist. All exported photos will be added to this album. This only works if the Photos library being exported is the last-opened (default) library in Photos. This feature is currently experimental. I dont know how well it will work on large export sets.</p>
@@ -1209,6 +1229,11 @@ to modify this behavior.</p>
<dd><p>If specified, saves the config file but does not export any files; must be used with save-config.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-print">
<span class="sig-name descname"><span class="pre">--print</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-print" title="Permalink to this definition">#</a></dt>
<dd><p>Render TEMPLATE string for each photo being exported and print to stdout. TEMPLATE is an osxphotos template string. This may be useful for creating custom reports, etc. TEMPLATE will be printed after the photo is exported or skipped. May be repeated to print multiple template strings.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-export-theme">
<span class="sig-name descname"><span class="pre">--theme</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;THEME&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-export-theme" title="Permalink to this definition">#</a></dt>
<dd><p>Specify the color theme to use for verbose output. Valid themes are dark, light, mono, and plain. Defaults to dark or light depending on system dark mode setting.</p>
@@ -1295,7 +1320,7 @@ to modify this behavior.</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-delete-file">
<span class="sig-name descname"><span class="pre">--delete-file</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;FILE_PATH&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-exportdb-delete-file" title="Permalink to this definition">#</a></dt>
<dd><p>Delete all data associated with FILE_PATH from the database.</p>
<dd><p>Delete all data associated with FILE_PATH from the database; does not delete the actual exported file if it exists, only the data in the database.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-exportdb-report">
@@ -1357,6 +1382,135 @@ to modify this behavior.</p>
<dd><p>Optional argument</p>
</dd></dl>
</section>
<section id="osxphotos-import">
<h3>import<a class="headerlink" href="#osxphotos-import" title="Permalink to this headline">#</a></h3>
<p>Import photos and videos into Photos.</p>
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>osxphotos import <span class="o">[</span>OPTIONS<span class="o">]</span> <span class="o">[</span>FILES<span class="o">]</span>...
</pre></div>
</div>
<p class="rubric">Options</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-a">
<span id="cmdoption-osxphotos-import-album"></span><span class="sig-name descname"><span class="pre">-a</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--album</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;ALBUM_TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-a" title="Permalink to this definition">#</a></dt>
<dd><p>Import photos into album ALBUM_TEMPLATE. ALBUM_TEMPLATE is an osxphotos template string. Photos may be imported into more than one album by repeating album. See Template System in help for additional information.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-t">
<span id="cmdoption-osxphotos-import-title"></span><span class="sig-name descname"><span class="pre">-t</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--title</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;TITLE_TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-t" title="Permalink to this definition">#</a></dt>
<dd><p>Set title of imported photos to TITLE_TEMPLATE. TITLE_TEMPLATE is a an osxphotos template string. See Template System in help for additional information.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-d">
<span id="cmdoption-osxphotos-import-description"></span><span class="sig-name descname"><span class="pre">-d</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--description</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;DESCRIPTION_TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-d" title="Permalink to this definition">#</a></dt>
<dd><p>Set description of imported photos to DESCRIPTION_TEMPLATE. DESCRIPTION_TEMPLATE is a an osxphotos template string. See Template System in help for additional information.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-k">
<span id="cmdoption-osxphotos-import-keyword"></span><span class="sig-name descname"><span class="pre">-k</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--keyword</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;KEYWORD_TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-k" title="Permalink to this definition">#</a></dt>
<dd><p>Set keywords of imported photos to KEYWORD_TEMPLATE. KEYWORD_TEMPLATE is a an osxphotos template string. More than one keyword may be set by repeating keyword. See Template System in help for additional information.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-m">
<span id="cmdoption-osxphotos-import-merge-keywords"></span><span class="sig-name descname"><span class="pre">-m</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--merge-keywords</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-m" title="Permalink to this definition">#</a></dt>
<dd><p>Merge keywords created by exiftool or keyword with any keywords already associated with the photo. Without merge-keywords, existing keywords will be overwritten.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-l">
<span id="cmdoption-osxphotos-import-location"></span><span class="sig-name descname"><span class="pre">-l</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--location</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;LATITUDE</span> <span class="pre">LONGITUDE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-l" title="Permalink to this definition">#</a></dt>
<dd><p>Set location of imported photo to LATITUDE LONGITUDE. Latitude is a number in the range -90.0 to 90.0; positive latitudes are north of the equator, negative latitudes are south of the equator. Longitude is a number in the range -180.0 to 180.0; positive longitudes are east of the Prime Meridian; negative longitudes are west of the Prime Meridian.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-C">
<span id="cmdoption-osxphotos-import-c"></span><span id="cmdoption-osxphotos-import-clear-metadata"></span><span class="sig-name descname"><span class="pre">-C</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--clear-metadata</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-C" title="Permalink to this definition">#</a></dt>
<dd><p>Clear any metadata set automatically by Photos upon import. Normally, Photos will set title, description, and keywords from XMP metadata in the imported file. If you specify clear-metadata, any metadata set by Photos will be cleared after import.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-L">
<span id="cmdoption-osxphotos-import-clear-location"></span><span class="sig-name descname"><span class="pre">-L</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--clear-location</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-L" title="Permalink to this definition">#</a></dt>
<dd><p>Clear any location data automatically imported by Photos. Normally, Photos will set location of the photo to the location data found in the metadata in the imported file. If you specify clear-location, this data will be cleared after import.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-e">
<span id="cmdoption-osxphotos-import-exiftool"></span><span class="sig-name descname"><span class="pre">-e</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--exiftool</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-e" title="Permalink to this definition">#</a></dt>
<dd><p>Use third party tool exiftool (<a class="reference external" href="https://exiftool.org/">https://exiftool.org/</a>) to automatically update metadata (title, description, keywords, location) in imported photos from the imported files metadata. Note: importing keywords from video files is not currently supported.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-p">
<span id="cmdoption-osxphotos-import-exiftool-path"></span><span class="sig-name descname"><span class="pre">-p</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--exiftool-path</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;EXIFTOOL_PATH&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-p" title="Permalink to this definition">#</a></dt>
<dd><p>Optionally specify path to exiftool; if not provided, will look for exiftool in $PATH.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-r">
<span id="cmdoption-osxphotos-import-relative-to"></span><span class="sig-name descname"><span class="pre">-r</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--relative-to</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;RELATIVE_TO_PATH&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-r" title="Permalink to this definition">#</a></dt>
<dd><p>If set, the {filepath} template will be computed relative to RELATIVE_TO_PATH. For example, if path to import is /Volumes/photos/import/album/img_1234.jpg then {filepath} will be this same value. If you set relative-to /Volumes/photos/import then {filepath} will be set to album/img_1234.jpg</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-D">
<span id="cmdoption-osxphotos-import-dup-check"></span><span class="sig-name descname"><span class="pre">-D</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--dup-check</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-D" title="Permalink to this definition">#</a></dt>
<dd><p>Check for duplicates on import.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-f">
<span id="cmdoption-osxphotos-import-split-folder"></span><span class="sig-name descname"><span class="pre">-f</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--split-folder</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;split_folder&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-f" title="Permalink to this definition">#</a></dt>
<dd><p>Automatically create hierarchal folders for albums as needed by splitting album name into folders and album. You must specify the character used to split folders and albums. For example, split-folder “/”’ will split the album name Folder/Album into folder Folder and album Album.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-w">
<span id="cmdoption-osxphotos-import-walk"></span><span class="sig-name descname"><span class="pre">-w</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--walk</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-w" title="Permalink to this definition">#</a></dt>
<dd><p>Recursively walk through directories.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-g">
<span id="cmdoption-osxphotos-import-glob"></span><span class="sig-name descname"><span class="pre">-g</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--glob</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;GLOB&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-g" title="Permalink to this definition">#</a></dt>
<dd><p>Only import files matching GLOB. GLOB is a Unix shell-style glob pattern, for example: glob “<a href="#id1"><span class="problematic" id="id2">*</span></a>.jpg”. GLOB may be repeated to import multiple patterns.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-report">
<span class="sig-name descname"><span class="pre">--report</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;REPORT_FILE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-report" title="Permalink to this definition">#</a></dt>
<dd><p>Write a report of all files that were imported. The extension of the report filename will be used to determine the format. Valid extensions are: .csv (CSV file), .json (JSON), .db and .sqlite (SQLite database). REPORT_FILE may be a template string (see Template System), for example, report export_{today.date}.csv will write a CSV report file named with todays date. See also append.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-append">
<span class="sig-name descname"><span class="pre">--append</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-append" title="Permalink to this definition">#</a></dt>
<dd><p>If used with report, add data to existing report file instead of overwriting it. See also report.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-V">
<span id="cmdoption-osxphotos-import-v"></span><span id="cmdoption-osxphotos-import-verbose"></span><span class="sig-name descname"><span class="pre">-V</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--verbose</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-V" title="Permalink to this definition">#</a></dt>
<dd><p>Print verbose output.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-T">
<span id="cmdoption-osxphotos-import-timestamp"></span><span class="sig-name descname"><span class="pre">-T</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--timestamp</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-T" title="Permalink to this definition">#</a></dt>
<dd><p>Add time stamp to verbose output</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-no-progress">
<span class="sig-name descname"><span class="pre">--no-progress</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-no-progress" title="Permalink to this definition">#</a></dt>
<dd><p>Do not display progress bar during import.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-check-templates">
<span class="sig-name descname"><span class="pre">--check-templates</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-check-templates" title="Permalink to this definition">#</a></dt>
<dd><p>Dont actually import anything; renders template strings so you can verify they are correct.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-theme">
<span class="sig-name descname"><span class="pre">--theme</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;THEME&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-import-theme" title="Permalink to this definition">#</a></dt>
<dd><p>Specify the color theme to use for verbose output. Valid themes are dark, light, mono, and plain. Defaults to dark or light depending on system dark mode setting.</p>
<dl class="field-list simple">
<dt class="field-odd">Options</dt>
<dd class="field-odd"><p>dark | light | mono | plain</p>
</dd>
</dl>
</dd></dl>
<p class="rubric">Arguments</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-import-arg-FILES">
<span id="cmdoption-osxphotos-import-arg-files"></span><span class="sig-name descname"><span class="pre">FILES</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-import-arg-FILES" title="Permalink to this definition">#</a></dt>
<dd><p>Optional argument(s)</p>
</dd></dl>
</section>
<section id="osxphotos-info">
<h3>info<a class="headerlink" href="#osxphotos-info" title="Permalink to this headline">#</a></h3>
<p>Print out descriptive info of the Photos library database.</p>
@@ -1398,6 +1552,11 @@ Works best with a modern terminal like iTerm2 or Kitty.</p>
<dd><p>Detect text in photos</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-inspect-T">
<span id="cmdoption-osxphotos-inspect-template"></span><span class="sig-name descname"><span class="pre">-T</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--template</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-inspect-T" title="Permalink to this definition">#</a></dt>
<dd><p>Template string to render for each photo using template preview mode. Useful for testing templates for export; may be repeated to test multiple templates. If template/-T is used, other inspection data will not be displayed.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-inspect-theme">
<span class="sig-name descname"><span class="pre">--theme</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;THEME&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-inspect-theme" title="Permalink to this definition">#</a></dt>
<dd><p>Specify the color theme to use for verbose output. Valid themes are dark, light, mono, and plain. Defaults to dark or light depending on system dark mode setting.</p>
@@ -1493,6 +1652,44 @@ Works best with a modern terminal like iTerm2 or Kitty.</p>
<dd><p>Print output in JSON format.</p>
</dd></dl>
</section>
<section id="osxphotos-orphans">
<h3>orphans<a class="headerlink" href="#osxphotos-orphans" title="Permalink to this headline">#</a></h3>
<p>Find orphaned photos in a Photos library</p>
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>osxphotos orphans <span class="o">[</span>OPTIONS<span class="o">]</span>
</pre></div>
</div>
<p class="rubric">Options</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-orphans-export">
<span class="sig-name descname"><span class="pre">--export</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;EXPORT_PATH&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-orphans-export" title="Permalink to this definition">#</a></dt>
<dd><p>Export orphans to directory EXPORT_PATH. If export not specified, orphans are listed but not exported.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-orphans-db">
<span class="sig-name descname"><span class="pre">--db</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;PHOTOS_LIBRARY_PATH&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-orphans-db" title="Permalink to this definition">#</a></dt>
<dd><p>Specify Photos database path. Path to Photos library/database can be specified using either db or directly as PHOTOS_LIBRARY positional argument. If neither db or PHOTOS_LIBRARY provided, will attempt to find the library to use in the following order: 1. last opened library, 2. system library, 3. ~/Pictures/Photos Library.photoslibrary</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-orphans-V">
<span id="cmdoption-osxphotos-orphans-v"></span><span id="cmdoption-osxphotos-orphans-verbose"></span><span class="sig-name descname"><span class="pre">-V</span></span><span class="sig-prename descclassname"></span><span class="sig-prename descclassname"><span class="pre">,</span> </span><span class="sig-name descname"><span class="pre">--verbose</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-orphans-V" title="Permalink to this definition">#</a></dt>
<dd><p>Print verbose output.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-orphans-timestamp">
<span class="sig-name descname"><span class="pre">--timestamp</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-orphans-timestamp" title="Permalink to this definition">#</a></dt>
<dd><p>Add time stamp to verbose output</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-orphans-theme">
<span class="sig-name descname"><span class="pre">--theme</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;THEME&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-orphans-theme" title="Permalink to this definition">#</a></dt>
<dd><p>Specify the color theme to use for verbose output. Valid themes are dark, light, mono, and plain. Defaults to dark or light depending on system dark mode setting.</p>
<dl class="field-list simple">
<dt class="field-odd">Options</dt>
<dd class="field-odd"><p>dark | light | mono | plain</p>
</dd>
</dl>
</dd></dl>
</section>
<section id="osxphotos-persons">
<h3>persons<a class="headerlink" href="#osxphotos-persons" title="Permalink to this headline">#</a></h3>
<p>Print out persons (faces) found in the Photos library.</p>
@@ -1866,6 +2063,11 @@ if more than one option is provided, they are treated as “AND”
<dd><p>Search for photos that were imported as referenced files (not copied into Photos library).</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-not-reference">
<span class="sig-name descname"><span class="pre">--not-reference</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-query-not-reference" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that are not references, that is, they were copied into the Photos library and are managed by Photos.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-in-album">
<span class="sig-name descname"><span class="pre">--in-album</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-query-in-album" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that are in one or more albums.</p>
@@ -1960,6 +2162,16 @@ if more than one option is provided, they are treated as “AND”
<span class="sig-name descname"><span class="pre">--add-to-album</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;ALBUM&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-query-add-to-album" title="Permalink to this definition">#</a></dt>
<dd><p>Add all photos from query to album ALBUM in Photos. Album ALBUM will be created if it doesnt exist. All photos in the query results will be added to this album. This only works if the Photos library being queried is the last-opened (default) library in Photos. This feature is currently experimental. I dont know how well it will work on large query sets.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-quiet">
<span class="sig-name descname"><span class="pre">--quiet</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-query-quiet" title="Permalink to this definition">#</a></dt>
<dd><p>Quiet output; doesnt actually print query results. Useful with print and add-to-album if you dont want to see the actual query results.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-print">
<span class="sig-name descname"><span class="pre">--print</span></span><span class="sig-prename descclassname"> <span class="pre">&lt;TEMPLATE&gt;</span></span><a class="headerlink" href="#cmdoption-osxphotos-query-print" title="Permalink to this definition">#</a></dt>
<dd><p>Render TEMPLATE string for each photo queried and print to stdout. TEMPLATE is an osxphotos template string. This may be useful for creating custom reports, etc. Most useful with quiet. May be repeated to print multiple template strings.</p>
</dd></dl>
<p class="rubric">Arguments</p>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-query-arg-PHOTOS_LIBRARY">
@@ -2290,6 +2502,11 @@ if more than one option is provided, they are treated as “AND”
<dd><p>Search for photos that were imported as referenced files (not copied into Photos library).</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-not-reference">
<span class="sig-name descname"><span class="pre">--not-reference</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-repl-not-reference" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that are not references, that is, they were copied into the Photos library and are managed by Photos.</p>
</dd></dl>
<dl class="std option">
<dt class="sig sig-object std" id="cmdoption-osxphotos-repl-in-album">
<span class="sig-name descname"><span class="pre">--in-album</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-repl-in-album" title="Permalink to this definition">#</a></dt>
<dd><p>Search for photos that are in one or more albums.</p>
@@ -2756,12 +2973,14 @@ Commands:
<li><a class="reference internal" href="#osxphotos-export">export</a></li>
<li><a class="reference internal" href="#osxphotos-exportdb">exportdb</a></li>
<li><a class="reference internal" href="#osxphotos-help">help</a></li>
<li><a class="reference internal" href="#osxphotos-import">import</a></li>
<li><a class="reference internal" href="#osxphotos-info">info</a></li>
<li><a class="reference internal" href="#osxphotos-inspect">inspect</a></li>
<li><a class="reference internal" href="#osxphotos-install">install</a></li>
<li><a class="reference internal" href="#osxphotos-keywords">keywords</a></li>
<li><a class="reference internal" href="#osxphotos-labels">labels</a></li>
<li><a class="reference internal" href="#osxphotos-list">list</a></li>
<li><a class="reference internal" href="#osxphotos-orphans">orphans</a></li>
<li><a class="reference internal" href="#osxphotos-persons">persons</a></li>
<li><a class="reference internal" href="#osxphotos-places">places</a></li>
<li><a class="reference internal" href="#osxphotos-query">query</a></li>

View File

@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.50.1 documentation</title>
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -122,7 +122,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -145,7 +145,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -269,6 +269,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-album">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-a">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-album">osxphotos-query command line option</a>
</li>
@@ -293,6 +295,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-append">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-append">osxphotos-exportdb command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-append">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -311,6 +315,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-check-signatures">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
--check-templates
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-check-templates">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -318,6 +329,20 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-cleanup">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--clear-location
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-L">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
--clear-metadata
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-C">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -408,6 +433,8 @@
<li><a href="cli.html#cmdoption-osxphotos-keywords-db">osxphotos-keywords command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-labels-db">osxphotos-labels command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-db">osxphotos-orphans command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-persons-db">osxphotos-persons command line option</a>
</li>
@@ -486,6 +513,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-description">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-d">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-description">osxphotos-query command line option</a>
</li>
@@ -531,6 +560,13 @@
<li><a href="cli.html#cmdoption-osxphotos-export-dry-run">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-dry-run">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
--dup-check
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-D">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -592,6 +628,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-exiftool">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-e">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -628,8 +666,17 @@
<li><a href="cli.html#cmdoption-osxphotos-exiftool-exiftool-path">osxphotos-exiftool command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-exiftool-path">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-p">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-e">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
--export
<ul>
<li><a href="cli.html#cmdoption-osxphotos-orphans-export">osxphotos-orphans command line option</a>
</li>
</ul></li>
<li>
@@ -682,6 +729,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-favorite">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-favorite">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--favorite-rating
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-favorite-rating">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
@@ -759,6 +813,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-F">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
--glob
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-g">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -932,6 +993,13 @@
<li><a href="cli.html#cmdoption-osxphotos-places-json">osxphotos-places command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-json">osxphotos-query command line option</a>
</li>
</ul></li>
<li>
--keep
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-keep">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
@@ -939,6 +1007,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-keyword">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-k">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-keyword">osxphotos-query command line option</a>
</li>
@@ -1018,6 +1088,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-location">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-l">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-location">osxphotos-query command line option</a>
</li>
@@ -1040,6 +1112,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-max-size">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-max-size">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--merge-keywords
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-m">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -1153,6 +1232,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-no-progress">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-no-progress">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -1166,8 +1247,6 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-no-title">osxphotos-repl command line option</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--not-burst
@@ -1199,6 +1278,8 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-not-favorite">osxphotos-repl command line option</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--not-hdr
@@ -1281,6 +1362,17 @@
<li><a href="cli.html#cmdoption-osxphotos-query-not-portrait">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-portrait">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--not-reference
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-not-reference">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-reference">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-reference">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
@@ -1483,6 +1575,17 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-preview-suffix">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--print
<ul>
<li><a href="cli.html#cmdoption-osxphotos-dump-print">osxphotos-dump command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-print">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-print">osxphotos-query command line option</a>
</li>
</ul></li>
<li>
@@ -1519,6 +1622,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-query-function">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-query-function">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--quiet
<ul>
<li><a href="cli.html#cmdoption-osxphotos-query-quiet">osxphotos-query command line option</a>
</li>
</ul></li>
<li>
@@ -1544,6 +1654,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-regex">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-regex">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--relative-to
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-r">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -1564,6 +1681,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-report">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-report">osxphotos-exportdb command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-report">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -1707,6 +1826,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-slow-mo">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-slow-mo">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--split-folder
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-f">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -1728,6 +1854,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-diff-s">osxphotos-diff command line option</a>
</li>
</ul></li>
<li>
--template
<ul>
<li><a href="cli.html#cmdoption-osxphotos-inspect-T">osxphotos-inspect command line option</a>
</li>
</ul></li>
<li>
@@ -1737,8 +1870,12 @@
<li><a href="cli.html#cmdoption-osxphotos-exiftool-theme">osxphotos-exiftool command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-theme">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-theme">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-theme">osxphotos-inspect command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-theme">osxphotos-orphans command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-theme">osxphotos-timewarp command line option</a>
</li>
@@ -1775,6 +1912,10 @@
<li><a href="cli.html#cmdoption-osxphotos-exiftool-timestamp">osxphotos-exiftool command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-timestamp">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-T">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-timestamp">osxphotos-orphans command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-timestamp">osxphotos-timewarp command line option</a>
</li>
@@ -1791,6 +1932,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-title">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-t">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-title">osxphotos-query command line option</a>
</li>
@@ -1942,6 +2085,10 @@
<li><a href="cli.html#cmdoption-osxphotos-export-V">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-V">osxphotos-exportdb command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-V">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-V">osxphotos-orphans command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-V">osxphotos-timewarp command line option</a>
</li>
@@ -1953,6 +2100,13 @@
<li><a href="cli.html#cmdoption-osxphotos-v">osxphotos command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-version">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
--walk
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-w">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -1984,7 +2138,16 @@
-a
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-a">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-a">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
-C
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-C">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -1998,6 +2161,8 @@
-D
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-D">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-D">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2005,6 +2170,8 @@
-d
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-d">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-d">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2012,6 +2179,8 @@
-e
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-e">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-e">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2026,9 +2195,18 @@
-f
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-f">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-0">osxphotos-timewarp command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-uuid-f">osxphotos-uuid command line option</a>
</li>
</ul></li>
<li>
-g
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-g">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -2049,19 +2227,37 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-i">osxphotos-repl command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-i">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
-k
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-k">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
-L
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-L">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-L">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
-l
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-l">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
-m
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-m">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-m">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2083,6 +2279,8 @@
-p
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-p">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-p">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2091,6 +2289,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-diff-r">osxphotos-diff command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-r">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -2104,6 +2304,10 @@
-T
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-T">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-T">osxphotos-inspect command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-T">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2111,6 +2315,8 @@
-t
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-t">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">osxphotos-inspect command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-t">osxphotos-timewarp command line option</a>
@@ -2134,6 +2340,10 @@
<li><a href="cli.html#cmdoption-osxphotos-export-V">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-V">osxphotos-exportdb command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-V">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-V">osxphotos-orphans command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-V">osxphotos-timewarp command line option</a>
</li>
@@ -2143,6 +2353,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-v">osxphotos command line option</a>
</li>
</ul></li>
<li>
-w
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-w">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -2437,6 +2654,8 @@
</li>
</ul></li>
<li><a href="reference.html#osxphotos.ExportDB">ExportDB (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.ExportDBTemp">ExportDBTemp (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.ExportOptions">ExportOptions (class in osxphotos)</a>
</li>
@@ -2470,17 +2689,28 @@
<li><a href="reference.html#osxphotos.QueryOptions.favorite">(osxphotos.QueryOptions attribute)</a>
</li>
</ul></li>
<li><a href="reference.html#osxphotos.ExportOptions.favorite_rating">favorite_rating (osxphotos.ExportOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.FileUtilNoOp.file_sig">file_sig() (osxphotos.FileUtilNoOp class method)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoInfo.filename">filename (osxphotos.PhotoInfo property)</a>
</li>
<li><a href="reference.html#osxphotos.FileUtil">FileUtil (class in osxphotos)</a>
<li>
FILES
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-arg-FILES">osxphotos-import command line option</a>
</li>
<li><a href="reference.html#osxphotos.ExportOptions.fileutil">fileutil (osxphotos.ExportOptions attribute)</a>
</ul></li>
<li><a href="reference.html#osxphotos.FileUtil">FileUtil (class in osxphotos)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.ExportOptions.fileutil">fileutil (osxphotos.ExportOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.FileUtilNoOp">FileUtilNoOp (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoTemplate.filter_predicate">filter_predicate() (osxphotos.PhotoTemplate method)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.folder">folder (osxphotos.QueryOptions attribute)</a>
</li>
@@ -2821,10 +3051,10 @@
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_favorite">not_favorite (osxphotos.QueryOptions attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.QueryOptions.not_hdr">not_hdr (osxphotos.QueryOptions attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.QueryOptions.not_hidden">not_hidden (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_in_album">not_in_album (osxphotos.QueryOptions attribute)</a>
@@ -2838,6 +3068,8 @@
<li><a href="reference.html#osxphotos.QueryOptions.not_panorama">not_panorama (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_portrait">not_portrait (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_reference">not_reference (osxphotos.QueryOptions attribute)</a>
</li>
<li><a href="reference.html#osxphotos.QueryOptions.not_screenshot">not_screenshot (osxphotos.QueryOptions attribute)</a>
</li>
@@ -2932,6 +3164,8 @@
<li><a href="cli.html#cmdoption-osxphotos-dump-deleted-only">--deleted-only</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-dump-json">--json</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-dump-print">--print</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-dump-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
</li>
@@ -3062,6 +3296,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-external-edit">--external-edit</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-favorite">--favorite</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-favorite-rating">--favorite-rating</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-filename">--filename</a>
</li>
@@ -3100,6 +3336,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-jpeg-ext">--jpeg-ext</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-jpeg-quality">--jpeg-quality</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-keep">--keep</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-keyword">--keyword</a>
</li>
@@ -3154,6 +3392,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-not-panorama">--not-panorama</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-portrait">--not-portrait</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-reference">--not-reference</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-not-screenshot">--not-screenshot</a>
</li>
@@ -3194,6 +3434,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-preview-if-missing">--preview-if-missing</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-preview-suffix">--preview-suffix</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-print">--print</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-query-eval">--query-eval</a>
</li>
@@ -3338,6 +3580,91 @@
<li><a href="cli.html#cmdoption-osxphotos-help-arg-SUBTOPIC">SUBTOPIC</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-help-arg-TOPIC">TOPIC</a>
</li>
</ul></li>
<li>
osxphotos-import command line option
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-a">--album</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-append">--append</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-check-templates">--check-templates</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-L">--clear-location</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-C">--clear-metadata</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-d">--description</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-D">--dup-check</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-e">--exiftool</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-p">--exiftool-path</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-g">--glob</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-k">--keyword</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-l">--location</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-m">--merge-keywords</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-no-progress">--no-progress</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-r">--relative-to</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-report">--report</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-f">--split-folder</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-theme">--theme</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-T">--timestamp</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-t">--title</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-V">--verbose</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-w">--walk</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-a">-a</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-C">-C</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-d">-d</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-D">-D</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-e">-e</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-f">-f</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-g">-g</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-k">-k</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-l">-l</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-L">-L</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-m">-m</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-p">-p</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-r">-r</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-t">-t</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-T">-T</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-V">-V</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-w">-w</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-arg-FILES">FILES</a>
</li>
</ul></li>
<li>
@@ -3351,6 +3678,8 @@
<li><a href="cli.html#cmdoption-osxphotos-info-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
osxphotos-inspect command line option
@@ -3358,10 +3687,14 @@
<li><a href="cli.html#cmdoption-osxphotos-inspect-db">--db</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">--detect-text</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-T">--template</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-theme">--theme</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">-t</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-T">-T</a>
</li>
</ul></li>
<li>
@@ -3397,13 +3730,28 @@
<li><a href="cli.html#cmdoption-osxphotos-labels-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
osxphotos-list command line option
<ul>
<li><a href="cli.html#cmdoption-osxphotos-list-json">--json</a>
</li>
</ul></li>
<li>
osxphotos-orphans command line option
<ul>
<li><a href="cli.html#cmdoption-osxphotos-orphans-db">--db</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-export">--export</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-theme">--theme</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-timestamp">--timestamp</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-V">--verbose</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-V">-V</a>
</li>
</ul></li>
<li>
@@ -3541,6 +3889,8 @@
<li><a href="cli.html#cmdoption-osxphotos-query-not-panorama">--not-panorama</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-portrait">--not-portrait</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-reference">--not-reference</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-not-screenshot">--not-screenshot</a>
</li>
@@ -3563,10 +3913,14 @@
<li><a href="cli.html#cmdoption-osxphotos-query-place">--place</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-portrait">--portrait</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-print">--print</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-query-eval">--query-eval</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-query-function">--query-function</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-quiet">--quiet</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-regex">--regex</a>
</li>
@@ -3712,6 +4066,8 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-not-panorama">--not-panorama</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-portrait">--not-portrait</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-reference">--not-reference</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-not-screenshot">--not-screenshot</a>
</li>
@@ -4040,6 +4396,10 @@
<li><a href="cli.html#cmdoption-osxphotos-query-arg-PHOTOS_LIBRARY">osxphotos-query command line option</a>
</li>
</ul></li>
<li><a href="reference.html#osxphotos.PhotosAlbum">PhotosAlbum (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.PhotosAlbumPhotoScript">PhotosAlbumPhotoScript (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.PhotosDB">PhotosDB (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoTemplate">PhotoTemplate (class in osxphotos)</a>

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos" href="overview.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos 0.50.1 documentation</title>
<title>osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="#"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="#"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="#">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -242,12 +242,14 @@
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-export">export</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-exportdb">exportdb</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-help">help</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-import">import</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-info">info</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-inspect">inspect</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-install">install</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-keywords">keywords</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-labels">labels</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-list">list</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-orphans">orphans</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-persons">persons</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-places">places</a></li>
<li class="toctree-l3"><a class="reference internal" href="cli.html#osxphotos-query">query</a></li>

Binary file not shown.

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Tutorial" href="tutorial.html" /><link rel="prev" title="Welcome to OSXPhotoss documentation!" href="index.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>OSXPhotos - osxphotos 0.50.1 documentation</title>
<title>OSXPhotos - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos python API" href="reference.html" /><link rel="prev" title="OSXPhotos Template System" href="template_help.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>OSXPhotos Python Package Overview - osxphotos 0.50.1 documentation</title>
<title>OSXPhotos Python Package Overview - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">

View File

@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Python Module Index - osxphotos 0.50.1 documentation</title>
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Python Module Index - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -122,7 +122,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -145,7 +145,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="#" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Search - osxphotos 0.50.1 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Search - osxphotos 0.51.4 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
@@ -121,7 +121,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -144,7 +144,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="#" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Python Package Overview" href="package_overview.html" /><link rel="prev" title="OSXPhotos Command Line Interface (CLI)" href="cli.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>OSXPhotos Template System - osxphotos 0.50.1 documentation</title>
<title>OSXPhotos Template System - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -235,10 +235,15 @@
<li><p><code class="docutils literal notranslate"><span class="pre">rsort</span></code>: Sort list of values in reverse order, e.g. [a, b, c] =&gt; [c, b, a].</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">reverse</span></code>: Reverse order of values, e.g. [a, b, c] =&gt; [c, b, a].</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">uniq</span></code>: Remove duplicate values, e.g. [a, b, c, b, a] =&gt; [a, b, c].</p></li>
<li><p><cite>join(x)</cite>: Join list of values with delimiter x, e.g. join(:): [a, b, c] =&gt; a:b:c; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.</p></li>
<li><p><cite>join(x)</cite>: Join list of values with delimiter x, e.g. join(,): [a, b, c] =&gt; a,b,c; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is join() which joins values together with no delimiter. e.g. join(): [a, b, c] =&gt; abc.</p></li>
<li><p><cite>append(x)</cite>: Append x to list of values, e.g. append(d): [a, b, c] =&gt; [a, b, c, d].</p></li>
<li><p><cite>prepend(x)</cite>: Prepend x to list of values, e.g. prepend(d): [a, b, c] =&gt; [d, a, b, c].</p></li>
<li><p><cite>remove(x)</cite>: Remove x from list of values, e.g. remove(b): [a, b, c] =&gt; [a, c].</p></li>
<li><p><cite>slice(start:stop:step)</cite>: Slice list using same semantics as Pythons list slicing, e.g. slice(1:3): [a, b, c, d] =&gt; [b, c]; slice(1:4:2): [a, b, c, d] =&gt; [b, d]; slice(1:): [a, b, c, d] =&gt; [b, c, d]; slice(:-1): [a, b, c, d] =&gt; [a, b, c]; slice(::-1): [a, b, c, d] =&gt; [d, c, b, a]. See also sslice().</p></li>
<li><p><cite>sslice(start:stop:step)</cite>: [s(tring) slice] Slice values in a list using same semantics as Pythons string slicing, e.g. sslice(1:3):abcd =&gt; bc; sslice(1:4:2): abcd =&gt; bd, etc. See also slice().</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">filter(x)</span></code>: Filter list of values using predicate x; for example, <code class="docutils literal notranslate"><span class="pre">{folder_album|filter(contains</span> <span class="pre">Events)}</span></code> returns only folders/albums containing the word Events in their path.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">int</span></code>: Convert values in list to integer, e.g. 1.0 =&gt; 1. If value cannot be converted to integer, remove value from list. [1.1, x] =&gt; [1]. See also float.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">float</span></code>: Convert values in list to floating point number, e.g. 1 =&gt; 1.0. If value cannot be converted to float, remove value from list. [1, x] =&gt; [1.0]. See also int.</p></li>
</ul>
<p>e.g. if Photo keywords are <code class="docutils literal notranslate"><span class="pre">["FOO","bar"]</span></code>:</p>
<ul class="simple">
@@ -532,59 +537,70 @@
<tr class="row-odd"><td><p>{uuid}</p></td>
<td><p>Photos internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. 128FB4C6-0B16-4E7D-9108-FB2E90DA1546</p></td>
</tr>
<tr class="row-even"><td><p>{id}</p></td>
<tr class="row-even"><td><p>{shortuuid}</p></td>
<td><p>A shorter representation of photos internal universally unique identifier (UUID) for the photo, a 22-character string unique to the photo, e.g. JYsxugP9UjetmCbBCHXcmu</p></td>
</tr>
<tr class="row-odd"><td><p>{id}</p></td>
<td><p>A unique number for the photo based on its primary key in the Photos database. A sequential integer, e.g. 1, 2, 3…etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use {id:05d} which results in 00001, 00002, 00003…etc.</p></td>
</tr>
<tr class="row-odd"><td><p>{album_seq}</p></td>
<tr class="row-even"><td><p>{album_seq}</p></td>
<td><p>An integer, starting at 0, indicating the photos index (sequence) in the containing album. Only valid when used in a filename template and only when {album} or {folder_album} is used in the directory template. For example directory “{folder_album}” filename “{album<em>seq}</em>{original_name}”’. To start counting at a value other than 0, append append (starting_value) to the field name. For example, to start counting at 1 instead of 0: {album_seq(1)}. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use {album_seq:05d} which results in 00000, 00001, 00002…etc. To format while also using a starting value: {album_seq:05d(1)} which results in 0001, 00002…etc.This may result in incorrect sequences if you have duplicate albums with the same name; see also {folder_album_seq}.</p></td>
</tr>
<tr class="row-even"><td><p>{folder_album_seq}</p></td>
<tr class="row-odd"><td><p>{folder_album_seq}</p></td>
<td><p>An integer, starting at 0, indicating the photos index (sequence) in the containing album and folder path. Only valid when used in a filename template and only when {folder_album} is used in the directory template. For example directory “{folder_album}” filename “{folder_album<em>seq}</em>{original_name}”’. To start counting at a value other than 0, append (starting_value) to the field name. For example, to start counting at 1 instead of 0: {folder_album_seq(1)} May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use {folder_album_seq:05d} which results in 00000, 00001, 00002…etc. To format while also using a starting value: {folder_album_seq:05d(1)} which results in 0001, 00002…etc.This may result in incorrect sequences if you have duplicate albums with the same name in the same folder; see also {album_seq}.</p></td>
</tr>
<tr class="row-odd"><td><p>{comma}</p></td>
<tr class="row-even"><td><p>{comma}</p></td>
<td><p>A comma: ,</p></td>
</tr>
<tr class="row-even"><td><p>{semicolon}</p></td>
<tr class="row-odd"><td><p>{semicolon}</p></td>
<td><p>A semicolon: ;</p></td>
</tr>
<tr class="row-odd"><td><p>{questionmark}</p></td>
<tr class="row-even"><td><p>{questionmark}</p></td>
<td><p>A question mark: ?</p></td>
</tr>
<tr class="row-even"><td><p>{pipe}</p></td>
<tr class="row-odd"><td><p>{pipe}</p></td>
<td><p>A vertical pipe: |</p></td>
</tr>
<tr class="row-odd"><td><p>{openbrace}</p></td>
<tr class="row-even"><td><p>{openbrace}</p></td>
<td><p>An open brace: {</p></td>
</tr>
<tr class="row-even"><td><p>{closebrace}</p></td>
<tr class="row-odd"><td><p>{closebrace}</p></td>
<td><p>A close brace: }</p></td>
</tr>
<tr class="row-odd"><td><p>{openparens}</p></td>
<tr class="row-even"><td><p>{openparens}</p></td>
<td><p>An open parentheses: (</p></td>
</tr>
<tr class="row-even"><td><p>{closeparens}</p></td>
<tr class="row-odd"><td><p>{closeparens}</p></td>
<td><p>A close parentheses: )</p></td>
</tr>
<tr class="row-odd"><td><p>{openbracket}</p></td>
<tr class="row-even"><td><p>{openbracket}</p></td>
<td><p>An open bracket: [</p></td>
</tr>
<tr class="row-even"><td><p>{closebracket}</p></td>
<tr class="row-odd"><td><p>{closebracket}</p></td>
<td><p>A close bracket: ]</p></td>
</tr>
<tr class="row-odd"><td><p>{newline}</p></td>
<tr class="row-even"><td><p>{newline}</p></td>
<td><p>A newline: n</p></td>
</tr>
<tr class="row-even"><td><p>{lf}</p></td>
<tr class="row-odd"><td><p>{lf}</p></td>
<td><p>A line feed: n, alias for {newline}</p></td>
</tr>
<tr class="row-odd"><td><p>{cr}</p></td>
<tr class="row-even"><td><p>{cr}</p></td>
<td><p>A carriage return: r</p></td>
</tr>
<tr class="row-even"><td><p>{crlf}</p></td>
<td><p>a carriage return + line feed: rn</p></td>
<tr class="row-odd"><td><p>{crlf}</p></td>
<td><p>A carriage return + line feed: rn</p></td>
</tr>
<tr class="row-even"><td><p>{tab}</p></td>
<td><dl class="field-list simple">
<dt class="field-odd">A tab</dt>
<dd class="field-odd"><p>t</p>
</dd>
</dl>
</td>
</tr>
<tr class="row-odd"><td><p>{osxphotos_version}</p></td>
<td><p>The osxphotos version, e.g. 0.50.1</p></td>
<td><p>The osxphotos version, e.g. 0.51.4</p></td>
</tr>
<tr class="row-even"><td><p>{osxphotos_cmd_line}</p></td>
<td><p>The full command line used to run osxphotos</p></td>

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Command Line Interface (CLI)" href="cli.html" /><link rel="prev" title="OSXPhotos" href="overview.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>OSXPhotos Tutorial - osxphotos 0.50.1 documentation</title>
<title>OSXPhotos Tutorial - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.1 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.1 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -229,6 +229,14 @@
<span class="o">/</span><span class="n">path</span><span class="o">/</span><span class="n">to</span><span class="o">/</span><span class="n">export</span><span class="o">/</span><span class="n">Vacation</span><span class="o">/</span><span class="n">IMG_1234</span><span class="o">.</span><span class="n">JPG</span>
</pre></div>
</div>
<p>If your photos are organized in folders and albums in Photos you can preserve this structure on export by using the <code class="docutils literal notranslate"><span class="pre">{folder_album}</span></code> template field with the <code class="docutils literal notranslate"><span class="pre">--directory</span></code> option. For example, if you have a photo in the album <code class="docutils literal notranslate"><span class="pre">Vacation</span></code> which is in the <code class="docutils literal notranslate"><span class="pre">Travel</span></code> folder, the following command would export the photo to the <code class="docutils literal notranslate"><span class="pre">Travel/Vacation</span></code> directory:</p>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--directory</span> <span class="pre">"{folder_album}"</span></code></p>
<p>Photos can belong to more than one album. In this case, the template field <code class="docutils literal notranslate"><span class="pre">{folder_album}</span></code> will expand to all the album names that the photo belongs to. For example, if a photo belongs to the albums <code class="docutils literal notranslate"><span class="pre">Vacation</span></code> and <code class="docutils literal notranslate"><span class="pre">Travel</span></code>, the template field <code class="docutils literal notranslate"><span class="pre">{folder_album}</span></code> would expand to <code class="docutils literal notranslate"><span class="pre">Vacation</span></code>, <code class="docutils literal notranslate"><span class="pre">Travel</span></code>. If the photo belongs to no albums, the template field <code class="docutils literal notranslate"><span class="pre">{folder_album}</span></code> would expand to “_” (the default value).</p>
<p>All template fields including <code class="docutils literal notranslate"><span class="pre">{folder_album}</span></code> can be further filtered using a number of different filters. To convert all directory names to lower case for example, use the <code class="docutils literal notranslate"><span class="pre">lower</span></code> filter:</p>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--directory</span> <span class="pre">"{folder_album|lower}"</span></code></p>
<p>If all your photos were organized into various albums under a folder named <code class="docutils literal notranslate"><span class="pre">Events</span></code> but some where also included in other top-level albums and you wanted to export only the <code class="docutils literal notranslate"><span class="pre">Events</span></code> folder, you could use the <code class="docutils literal notranslate"><span class="pre">filter</span></code> option to filter out the other top-level albums by selecting only those folder/album paths that start with <code class="docutils literal notranslate"><span class="pre">Events</span></code>:</p>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--directory</span> <span class="pre">"{folder_album|filter(startswith</span> <span class="pre">Events)}"</span></code></p>
<p>You can learn more about the other filters using <code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">help</span> <span class="pre">export</span></code>.</p>
</section>
<section id="specify-exported-filename">
<h2>Specify exported filename<a class="headerlink" href="#specify-exported-filename" title="Permalink to this headline">#</a></h2>
@@ -367,6 +375,10 @@
<h2>Creating a report of all exported files<a class="headerlink" href="#creating-a-report-of-all-exported-files" title="Permalink to this headline">#</a></h2>
<p>You can use the <code class="docutils literal notranslate"><span class="pre">--report</span></code> option to create a report, in comma-separated values (CSV) format that will list the details of all files that were exported, skipped, missing, etc. This file format is compatible with programs such as Microsoft Excel. Provide the name of the report after the <code class="docutils literal notranslate"><span class="pre">--report</span></code> option:</p>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--report</span> <span class="pre">export.csv</span></code></p>
<p>You can also create reports in JSON or SQLite format by changing the extension of the report filename. For example, to create a JSON report:</p>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--report</span> <span class="pre">export.json</span></code></p>
<p>And to create a SQLite report:</p>
<p><code class="docutils literal notranslate"><span class="pre">osxphotos</span> <span class="pre">export</span> <span class="pre">/path/to/export</span> <span class="pre">--report</span> <span class="pre">export.sqlite</span></code></p>
</section>
<section id="exporting-only-certain-photos">
<h2>Exporting only certain photos<a class="headerlink" href="#exporting-only-certain-photos" title="Permalink to this headline">#</a></h2>

View File

@@ -55,10 +55,15 @@ Valid filters are:
* ``rsort``\ : Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
* ``reverse``\ : Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
* ``uniq``\ : Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].
* `join(x)`: Join list of values with delimiter x, e.g. join(:): ['a', 'b', 'c'] => 'a:b:c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.
* `join(x)`: Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.
* `append(x)`: Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].
* `prepend(x)`: Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].
* `remove(x)`: Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].
* `slice(start:stop:step)`: Slice list using same semantics as Python's list slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().
* `sslice(start:stop:step)`: [s(tring) slice] Slice values in a list using same semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().
* ``filter(x)``\ : Filter list of values using predicate x; for example, ``{folder_album|filter(contains Events)}`` returns only folders/albums containing the word 'Events' in their path.
* ``int``\ : Convert values in list to integer, e.g. 1.0 => 1. If value cannot be converted to integer, remove value from list. ['1.1', 'x'] => ['1']. See also float.
* ``float``\ : Convert values in list to floating point number, e.g. 1 => 1.0. If value cannot be converted to float, remove value from list. ['1', 'x'] => ['1.0']. See also int.
e.g. if Photo keywords are ``["FOO","bar"]``\ :
@@ -307,6 +312,8 @@ Template Substitutions
- The moment title of the photo
* - {uuid}
- Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'
* - {shortuuid}
- A shorter representation of photo's internal universally unique identifier (UUID) for the photo, a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'
* - {id}
- A unique number for the photo based on its primary key in the Photos database. A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. May be formatted using a python string format code. For example, to format as a 5-digit integer and pad with zeros, use '{id:05d}' which results in 00001, 00002, 00003...etc.
* - {album_seq}
@@ -340,9 +347,11 @@ Template Substitutions
* - {cr}
- A carriage return: '\r'
* - {crlf}
- a carriage return + line feed: '\r\n'
- A carriage return + line feed: '\r\n'
* - {tab}
- :A tab: '\t'
* - {osxphotos_version}
- The osxphotos version, e.g. '0.50.1'
- The osxphotos version, e.g. '0.51.4'
* - {osxphotos_cmd_line}
- The full command line used to run osxphotos
* - {album}

View File

@@ -73,6 +73,22 @@ the exported files would be:
/path/to/export/Vacation/IMG_1234.JPG
If your photos are organized in folders and albums in Photos you can preserve this structure on export by using the ``{folder_album}`` template field with the ``--directory`` option. For example, if you have a photo in the album ``Vacation`` which is in the ``Travel`` folder, the following command would export the photo to the ``Travel/Vacation`` directory:
``osxphotos export /path/to/export --directory "{folder_album}"``
Photos can belong to more than one album. In this case, the template field ``{folder_album}`` will expand to all the album names that the photo belongs to. For example, if a photo belongs to the albums ``Vacation`` and ``Travel``\ , the template field ``{folder_album}`` would expand to ``Vacation``\ , ``Travel``. If the photo belongs to no albums, the template field ``{folder_album}`` would expand to "_" (the default value).
All template fields including ``{folder_album}`` can be further filtered using a number of different filters. To convert all directory names to lower case for example, use the ``lower`` filter:
``osxphotos export /path/to/export --directory "{folder_album|lower}"``
If all your photos were organized into various albums under a folder named ``Events`` but some where also included in other top-level albums and you wanted to export only the ``Events`` folder, you could use the ``filter`` option to filter out the other top-level albums by selecting only those folder/album paths that start with ``Events``\ :
``osxphotos export /path/to/export --directory "{folder_album|filter(startswith Events)}"``
You can learn more about the other filters using ``osxphotos help export``.
Specify exported filename
-------------------------
@@ -275,6 +291,14 @@ You can use the ``--report`` option to create a report, in comma-separated value
``osxphotos export /path/to/export --report export.csv``
You can also create reports in JSON or SQLite format by changing the extension of the report filename. For example, to create a JSON report:
``osxphotos export /path/to/export --report export.json``
And to create a SQLite report:
``osxphotos export /path/to/export --report export.sqlite``
Exporting only certain photos
-----------------------------

43
examples/bad_photos.py Normal file
View File

@@ -0,0 +1,43 @@
""" Find 'bad photos' and add them to an album
This is inspired by this blog post: https://www.muchen.ca/blog/2022/cleanup-photos/
This is an osxphotos query function, that when run as follows,
will add all photos with low quality scores to the album 'Bad Photos'
osxphotos query --query-function bad_photos.py::bad_photos --add-to-album "Bad Photos"
"""
from typing import List
from osxphotos import PhotoInfo
# Call this with: osxphotos query --query-function bad_photos.py::bad_photos --add-to-album "Bad Photos"
def bad_photos(photos: List[PhotoInfo]) -> List[PhotoInfo]:
"""your query function should take a list of PhotoInfo objects and return a list of PhotoInfo objects (or empty list)"""
# this example finds bad photos (as measured by Photos' own scoring system)
# don't include screenshots as Photos tends to give low scores to screenshots
return [p for p in photos if not p.screenshot and is_bad_photo(p)]
def is_bad_photo(p: PhotoInfo) -> bool:
"""Look at photo's ScoreInfo to find photos that have low scores
(and hence might be considered bad photos)
"""
return any(
[
p.score.failure < -0.1,
p.score.harmonious_color < -0.1,
p.score.interesting_subject < -0.7,
p.score.intrusive_object_presence < -0.999,
p.score.noise < -0.75,
p.score.pleasant_composition < -0.8,
p.score.pleasant_lighting < -0.7,
p.score.pleasant_perspective < -0.6,
p.score.tastefully_blurred < -0.9,
p.score.well_framed_subject < -0.7,
p.score.well_timed_shot < -0.7,
]
)

View File

@@ -0,0 +1,42 @@
"""Compare two albums in Photos and find the differences."""
import sys
import osxphotos
import click
@click.command()
@click.argument("album1")
@click.argument("album2")
def compare_albums(album1, album2):
print("Loading Photos library...")
photosdb = osxphotos.PhotosDB()
album1_ = None
album2_ = None
for album in photosdb.album_info:
if album.title == album1:
album1_ = album
if album.title == album2:
album2_ = album
if album1_ is None:
print("Album 1 not found:", album1)
sys.exit(1)
if album2_ is None:
print("Album 2 not found:", album2)
sys.exit(1)
print(f"Comparing albums: '{album1}' '{album2}'")
not_in_album2 = [photo for photo in album1_.photos if photo not in album2_.photos]
not_in_album1 = [photo for photo in album2_.photos if photo not in album1_.photos]
print(f"Photos in '{album1}' but not in '{album2}':")
for photo in not_in_album2:
print(f" {photo.original_filename}, {photo.date}, {photo.uuid}")
print(f"Photos in '{album2}' but not in '{album1}':")
for photo in not_in_album1:
print(f" {photo.original_filename}, {photo.date}, {photo.uuid}")
if __name__ == "__main__":
compare_albums()

View File

@@ -0,0 +1,40 @@
""" Find photos that had an EXIF or XMP rating of 5 and mark them as favorites in Photos
To use this script, save it to a file, e.g. `rating_to_favorites.py` then
run it with osxphotos (https://github.com/RhetTbull/osxphotos) via
`osxphotos run rating_to_favorites.py`
You'll also need exiftool (https://exiftool.org/)
"""
import photoscript
import osxphotos
# only find photos taken with SONY cameras, adjust to suit your use case
CAMERA_MAKE = "SONY"
def main():
"""Find all photos with EXIF or XMP rating of 5 and mark them as favorites"""
photosdb = osxphotos.PhotosDB()
for photo in photosdb.photos():
# extracting the rating data takes a while so
# skip photos not taken with the camera we're looking for
if photo.exif_info.camera_make != CAMERA_MAKE:
continue
# Photos stores some data in photo.exif but the rating data must be extracted with exiftool
if exif := photo.exiftool:
exif_data = exif.asdict()
# I think SONY uses XMP:Rating but also check EXIF:Rating
xmp_rating = exif_data.get("XMP:Rating", 0)
exif_rating = exif_data.get("EXIF:Rating", 0)
rating = max(xmp_rating, exif_rating)
if rating == 5:
print(f"Marking {photo.original_filename} ({photo.uuid}) as favorite")
photoscript.Photo(photo.uuid).favorite = True
if __name__ == "__main__":
main()

196
examples/strip_live.py Normal file
View File

@@ -0,0 +1,196 @@
"""Export selected Live photos and re-import just the image portion"""
import pathlib
import sys
import time
from tempfile import TemporaryDirectory
from typing import List
import click
from photoscript import Album, Photo, PhotosLibrary
from rich import print
from osxphotos import PhotoInfo, PhotosDB
DEFAULT_DELETE_ALBUM = "Live Photos to Delete"
DEFAULT_NEW_ALBUM = "Imported Live Photos"
def rename_photos(photo_paths: List[str]) -> List[str]:
"""Given a list of photo paths, rename the photos so names don't clash as duplicated on re-import"""
# use perf_counter_ns as a simple unique ID to ensure each photo has a different name
new_paths = []
for path in photo_paths:
path = pathlib.Path(path)
stem = f"{path.stem}_{time.perf_counter_ns()}"
new_path = path.rename(path.parent / f"{stem}{path.suffix}")
new_paths.append(str(new_path))
return new_paths
def set_metadata_from_photo(source_photo: PhotoInfo, dest_photos: List[Photo]):
"""Set metadata (keywords, albums, title, description, favorite) for dest_photos from source_photo"""
title = source_photo.title
description = source_photo.description
keywords = source_photo.keywords
favorite = source_photo.favorite
# apply metadata to each photo
for dest_photo in dest_photos:
dest_photo.title = title
dest_photo.description = description
dest_photo.keywords = keywords
dest_photo.favorite = favorite
# add photos to albums
album_ids = [a.uuid for a in source_photo.album_info]
for album_id in album_ids:
album = Album(album_id)
album.add(dest_photos)
def process_photo(
photo: Photo,
photosdb: PhotosDB,
keep_originals: bool,
download_missing: bool,
new_album: Album,
delete_album: Album,
):
"""Process each Live Photo to export/re-import it"""
with TemporaryDirectory() as tempdir:
p = photosdb.get_photo(photo.uuid)
if not p.live_photo:
print(
f"[yellow]Skipping non-Live photo {p.original_filename} ({p.uuid})[/]"
)
return
# versions to download (True for edited, False for original)
versions = []
# use photos_export to download from iCloud
photos_export = False
# try to download missing photos only if photo is missing and --download-missing
if keep_originals or not p.hasadjustments:
# export original photo
if not p.path and not download_missing:
print(
f"[yellow]Skipping missing original version of photo {p.original_filename} ({p.uuid}) (you may want to try --download-missing)[/]"
)
return
photos_export = download_missing and not p.path
versions.append(False)
if p.hasadjustments:
if not p.path_edited and not download_missing:
print(
f"[yellow]Skipping missing edited version of photo {p.original_filename} ({p.uuid}) (you may want to try --download-missing)[/]"
)
return
photos_export = photos_export or (download_missing and not p.path_edited)
versions.append(True)
exported = []
for version in versions:
# export the actual photo (without the Live video)
print(
f"Exporting {'edited' if version else 'original'} photo {p.original_filename} ({p.uuid})"
)
if exports := p.export(
tempdir,
live_photo=False,
edited=version,
use_photos_export=photos_export,
):
exported.extend(exports)
else:
print(
f"[red]Error exporting photo {p.original_filename} ({p.uuid})[/]",
file=sys.stderr,
)
if not exported:
return
exported = rename_photos(exported)
print(
f"Re-importing {', '.join([pathlib.Path(p).name for p in exported])} to album '{new_album.name}'"
)
new_photos = new_album.import_photos(exported)
print("Applying metadata to newly imported photos")
set_metadata_from_photo(p, new_photos)
print(f"Moving {p.original_filename} to album '{delete_album.name}'")
delete_album.add([Photo(p.uuid)])
@click.command()
@click.option(
"--download-missing", is_flag=True, help="Download missing files from iCloud."
)
@click.option(
"--keep-originals",
is_flag=True,
help="If photo is edited, also keep the original, unedited photo. "
"Without --keep-originals, only the edited version of a Live photo that has been edited will be kept.",
)
@click.option(
"--delete-album",
"delete_album_name",
default=DEFAULT_DELETE_ALBUM,
help="Album to put Live photos in when they're ready to be deleted; "
f"default = '{DEFAULT_DELETE_ALBUM}'",
)
@click.option(
"--new-album",
"new_album_name",
default=DEFAULT_NEW_ALBUM,
help="Album to put Live photos in when they've been re-imported after stripping the video component; "
f"default = '{DEFAULT_NEW_ALBUM}'",
)
def strip_live_photos(
download_missing, keep_originals, delete_album_name, new_album_name
):
"""Export selected Live photos and re-import just the image portion.
This script can be used to free space in your Photos library by allowing you
to effectively delete just the Live video portion of a Live photo.
The photo part of the Live photo will be exported to a temporary directory then
reimported into Photos. Albums, keywords, title/caption, favorite, and description
will be preserved. Unfortunately person/face data cannot be preserved.
After export Live photos will be moved to an album (which can be set using
--delete-album) so they can be deleted. You can use Command + Delete to put the
photos in the trash after selecting them in the album.
"""
photoslib = PhotosLibrary()
selected = photoslib.selection
if not selected:
print("No photos selected...nothing to do", file=sys.stderr)
sys.exit(1)
print(f"Processing {len(selected)} photo(s)")
print("Loading Photos database")
photosdb = PhotosDB()
new_album = photoslib.album(
new_album_name, top_level=True
) or photoslib.create_album(new_album_name)
delete_album = photoslib.album(
delete_album_name, top_level=True
) or photoslib.create_album(delete_album_name)
for photo in selected:
process_photo(
photo, photosdb, keep_originals, download_missing, new_album, delete_album
)
new_album.spotlight()
if __name__ == "__main__":
strip_live_photos()

View File

@@ -0,0 +1,27 @@
""" Example showing how to use a custom function for osxphotos {function} template with the `osxphotos import` command
Use: osxphotos import /path/to/import/*.jpg --album "{function:/path/to/template_function_import.py::example}"
You may place more than one template function in a single file as each is called by name using the {function:file.py::function_name} format
"""
import pathlib
from typing import List, Optional, Union
def example(
filepath: pathlib.Path, args: Optional[str] = None, **kwargs
) -> Union[List, str]:
"""example function for {function} template for use with `osxphotos import`
This example parses filenames in format album_img_123.jpg and returns the album name
Args:
filepath: pathlib.Path object of file being imported
args: optional str of arguments passed to template function
**kwargs: not currently used, placeholder to keep functions compatible with possible changes to {function}
Returns:
str or list of str of values that should be substituted for the {function} template
"""
filename = filepath.stem
fields = filename.split("_")
return fields[0] if len(fields) > 1 else ""

93
examples/xmp_rating.py Normal file
View File

@@ -0,0 +1,93 @@
""" Example function for use with osxphotos export --post-function option to set custom XMP:Rating value"""
# See this Reddit post for context: https://www.reddit.com/r/osxphotos/comments/wo4xra/can_i_set_xmprating_based_on_keywords/
import sys
from typing import Callable
from osxphotos import ExportResults, PhotoInfo
from osxphotos.exiftool import ExifTool
from osxphotos.utils import normalize_unicode
# Update this for your custom keyword to rating mapping
RATINGS = {
"★⭐︎⭐︎⭐︎⭐︎": 1,
"★★︎⭐︎⭐︎⭐︎": 2,
"★★★︎⭐︎⭐︎": 3,
"★★★★︎⭐︎": 4,
"★★★★★︎": 5,
}
# normalize the unicode to match what osxphotos uses internally
RATINGS = {normalize_unicode(k): v for k, v in RATINGS.items()}
def rating(photo: PhotoInfo, results: ExportResults, verbose: Callable, **kwargs):
"""Call this with `osxphotos export /path/to/export --post-function xmp_rating.py::rating`
This will get called immediately after the photo has been exported
Args:
photo: PhotoInfo instance for the photo that's just been exported
results: ExportResults instance with information about the files associated with the exported photo
verbose: A function to print verbose output if --verbose is set; if --verbose is not set, acts as a no-op (nothing gets printed)
**kwargs: reserved for future use; recommend you include **kwargs so your function still works if additional arguments are added in future versions
Notes:
Use verbose(str) instead of print if you want your function to conditionally output text depending on --verbose flag
Any string printed with verbose that contains "warning" or "error" (case-insensitive) will be printed with the appropriate warning or error color
Will not be called if --dry-run flag is enabled
Will be called immediately after export and before any --post-command commands are executed
"""
# ExportResults has the following properties
# fields with filenames contain the full path to the file
# exported: list of all files exported
# new: list of all new files exported (--update)
# updated: list of all files updated (--update)
# skipped: list of all files skipped (--update)
# exif_updated: list of all files that were updated with --exiftool
# touched: list of all files that had date updated with --touch-file
# converted_to_jpeg: list of files converted to jpeg with --convert-to-jpeg
# sidecar_json_written: list of all JSON sidecar files written
# sidecar_json_skipped: list of all JSON sidecar files skipped (--update)
# sidecar_exiftool_written: list of all exiftool sidecar files written
# sidecar_exiftool_skipped: list of all exiftool sidecar files skipped (--update)
# sidecar_xmp_written: list of all XMP sidecar files written
# sidecar_xmp_skipped: list of all XMP sidecar files skipped (--update)
# missing: list of all missing files
# error: list tuples of (filename, error) for any errors generated during export
# exiftool_warning: list of tuples of (filename, warning) for any warnings generated by exiftool with --exiftool
# exiftool_error: list of tuples of (filename, error) for any errors generated by exiftool with --exiftool
# xattr_written: list of files that had extended attributes written
# xattr_skipped: list of files that where extended attributes were skipped (--update)
# deleted_files: list of deleted files
# deleted_directories: list of deleted directories
# exported_album: list of tuples of (filename, album_name) for exported files added to album with --add-exported-to-album
# skipped_album: list of tuples of (filename, album_name) for skipped files added to album with --add-skipped-to-album
# missing_album: list of tuples of (filename, album_name) for missing files added to album with --add-missing-to-album
# metadata_changed: list of filenames that had metadata changes since last export
xmp_rating = None
# check to see if there's a rating to apply
for rating in RATINGS:
if rating in photo.keywords:
xmp_rating = RATINGS[rating]
break
if not xmp_rating:
# nothing to do
verbose("No XMP:Rating to set")
return
# update each exported file with the new rating
for filename in results.exported:
verbose(
f"Updating [filepath]{filename}[/] with XMP:Rating=[num]{xmp_rating}[/]"
)
with ExifTool(filename) as exiftool:
if not exiftool.setvalue("XMP:Rating", xmp_rating):
print(
f"Error updating XMP:Rating for file {filename}: {exiftool.error}",
file=sys.stderr,
)

View File

@@ -2,16 +2,17 @@ import logging
from ._constants import AlbumSortOrder
from ._version import __version__
from .albuminfo import AlbumInfo, ImportInfo, ProjectInfo, FolderInfo
from .albuminfo import AlbumInfo, FolderInfo, ImportInfo, ProjectInfo
from .debug import is_debug, set_debug
from .exifinfo import ExifInfo
from .exiftool import ExifTool
from .export_db import ExportDB
from .export_db import ExportDB, ExportDBTemp
from .fileutil import FileUtil, FileUtilNoOp
from .momentinfo import MomentInfo
from .personinfo import PersonInfo
from .photoexporter import ExportOptions, ExportResults, PhotoExporter
from .photoinfo import PhotoInfo
from .photosalbum import PhotosAlbum, PhotosAlbumPhotoScript
from .photosdb import PhotosDB
from .photosdb._photosdb_process_comments import CommentInfo, LikeInfo
from .phototemplate import PhotoTemplate
@@ -43,6 +44,8 @@ __all__ = [
"PhotoExporter",
"PhotoInfo",
"PhotoTemplate",
"PhotosAlbum",
"PhotosAlbumPhotoScript",
"PhotosDB",
"PlaceInfo",
"ProjectInfo",

View File

@@ -44,10 +44,8 @@ _PHOTOS_5_VERSION = "5000" # I've seen both 5001 and 6000. 6000 is most common
# Ranges for model version by Photos version
_PHOTOS_5_MODEL_VERSION = [13000, 13999]
_PHOTOS_6_MODEL_VERSION = [14000, 14999]
_PHOTOS_7_MODEL_VERSION = [
15000,
15999,
] # Monterey developer preview is 15134, 12.1 is 15331
_PHOTOS_7_MODEL_VERSION = [15000, 15999] # Dev preview: 15134, 12.1: 15331
_PHOTOS_8_MODEL_VERSION = [16000, 16999] # Ventura dev preview: 16119
# some table names differ between Photos 5 and Photos 6
_DB_TABLE_NAMES = {
@@ -87,6 +85,18 @@ _DB_TABLE_NAMES = {
"ASSET_ALBUM_TABLE": "Z_27ASSETS",
"HDR_TYPE": "ZHDRTYPE",
},
8: {
"ASSET": "ZASSET",
"KEYWORD_JOIN": "Z_1KEYWORDS.Z_40KEYWORDS",
"ALBUM_JOIN": "Z_28ASSETS.Z_3ASSETS",
"ALBUM_SORT_ORDER": "Z_28ASSETS.Z_FOK_3ASSETS",
"IMPORT_FOK": "null",
"DEPTH_STATE": "ZASSET.ZDEPTHTYPE",
"UTI_ORIGINAL": "ZINTERNALRESOURCE.ZCOMPACTUTI",
"ASSET_ALBUM_JOIN": "Z_28ASSETS.Z_28ALBUMS",
"ASSET_ALBUM_TABLE": "Z_28ASSETS",
"HDR_TYPE": "ZHDRTYPE",
},
}
# which version operating systems have been tested
@@ -107,6 +117,8 @@ _TESTED_OS_VERSIONS = [
("12", "1"),
("12", "2"),
("12", "3"),
("12", "4"),
("12", "5"),
]
# Photos 5 has persons who are empty string if unidentified face

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.50.1"
__version__ = "0.51.4"

View File

@@ -58,6 +58,7 @@ from .install_uninstall_run import install, run, uninstall
from .keywords import keywords
from .labels import labels
from .list import _list_libraries, list_libraries
from .orphans import orphans
from .persons import persons
from .photo_inspect import photo_inspect
from .places import places
@@ -88,6 +89,7 @@ __all__ = [
"list_libraries",
"list_libraries",
"load_uuid_from_file",
"orphans",
"persons",
"photo_inspect",
"places",

View File

@@ -15,11 +15,13 @@ from .export import export
from .exportdb import exportdb
from .grep import grep
from .help import help
from .import_cli import import_cli
from .info import info
from .install_uninstall_run import install, run, uninstall
from .keywords import keywords
from .labels import labels
from .list import list_libraries
from .orphans import orphans
from .persons import persons
from .photo_inspect import photo_inspect
from .places import places
@@ -74,11 +76,13 @@ for command in [
exportdb,
grep,
help,
import_cli,
info,
install,
keywords,
labels,
list_libraries,
orphans,
persons,
photo_inspect,
places,

View File

@@ -35,6 +35,7 @@ __all__ = [
"DB_OPTION",
"DEBUG_OPTIONS",
"DELETED_OPTIONS",
"FIELD_OPTION",
"JSON_OPTION",
"QUERY_OPTIONS",
"THEME_OPTION",
@@ -116,6 +117,19 @@ JSON_OPTION = click.option(
help="Print output in JSON format.",
)
FIELD_OPTION = click.option(
"--field",
"-f",
metavar="FIELD TEMPLATE",
multiple=True,
nargs=2,
help="Output only specified custom fields. "
"FIELD is the name of the field and TEMPLATE is the template to use as the field value. "
"May be repeated to output multiple fields. "
"For example, to output photo uuid, name, and title: "
'`--field uuid "{uuid}" --field name "{original_name}" --field title "{title}"`.',
)
def DELETED_OPTIONS(f):
o = click.option
@@ -420,6 +434,12 @@ def QUERY_OPTIONS(f):
is_flag=True,
help="Search for photos that were imported as referenced files (not copied into Photos library).",
),
o(
"--not-reference",
is_flag=True,
help="Search for photos that are not references, that is, they were copied into the Photos library "
"and are managed by Photos.",
),
o(
"--in-album",
is_flag=True,

View File

@@ -3,24 +3,66 @@
import click
import osxphotos
from osxphotos.cli.click_rich_echo import (
rich_click_echo,
set_rich_console,
set_rich_theme,
)
from osxphotos.phototemplate import RenderOptions
from osxphotos.queryoptions import QueryOptions
from .common import DB_ARGUMENT, DB_OPTION, DELETED_OPTIONS, JSON_OPTION, get_photos_db
from .color_themes import get_default_theme
from .common import (
DB_ARGUMENT,
DB_OPTION,
DELETED_OPTIONS,
FIELD_OPTION,
JSON_OPTION,
get_photos_db,
)
from .list import _list_libraries
from .print_photo_info import print_photo_info
from .print_photo_info import print_photo_fields, print_photo_info
from .verbose import get_verbose_console
@click.command()
@DB_OPTION
@JSON_OPTION
@DELETED_OPTIONS
@FIELD_OPTION
@click.option(
"--print",
"print_template",
metavar="TEMPLATE",
multiple=True,
help="Render TEMPLATE string for each photo queried and print to stdout. "
"TEMPLATE is an osxphotos template string. "
"This may be useful for creating custom reports, etc. "
"If --print TEMPLATE is provided, regular output is suppressed "
"and only the rendered TEMPLATE values are printed. "
"May be repeated to print multiple template strings. ",
)
@DB_ARGUMENT
@click.pass_obj
@click.pass_context
def dump(ctx, cli_obj, db, json_, deleted, deleted_only, photos_library):
def dump(
ctx,
cli_obj,
db,
deleted,
deleted_only,
field,
json_,
photos_library,
print_template,
):
"""Print list of all photos & associated info from the Photos library."""
db = get_photos_db(*photos_library, db, cli_obj.db)
# below needed for to make CliRunner work for testing
cli_db = cli_obj.db if cli_obj is not None else None
cli_json = cli_obj.json if cli_obj is not None else None
db = get_photos_db(*photos_library, db, cli_db)
if db is None:
click.echo(ctx.obj.group.commands["dump"].get_help(ctx), err=True)
click.echo("\n\nLocated the following Photos library databases: ", err=True)
@@ -33,6 +75,10 @@ def dump(ctx, cli_obj, db, json_, deleted, deleted_only, photos_library):
click.echo(ctx.obj.group.commands["dump"].get_help(ctx), err=True)
return
# set console for rich_echo to be same as for verbose_
set_rich_console(get_verbose_console())
set_rich_theme(get_default_theme())
photosdb = osxphotos.PhotosDB(dbfile=db)
if deleted or deleted_only:
photos = photosdb.photos(movies=True, intrash=True)
@@ -41,4 +87,28 @@ def dump(ctx, cli_obj, db, json_, deleted, deleted_only, photos_library):
if not deleted_only:
photos += photosdb.photos(movies=True)
print_photo_info(photos, json_ or cli_obj.json)
if not print_template and not field:
# just dump and be done
print_photo_info(photos, cli_json or json_)
return
if field:
print_photo_fields(photos, field, cli_json or json_)
if print_template:
# have print template(s)
options = RenderOptions()
for p in photos:
for template in print_template:
rendered_templates, unmatched = p.render_template(
template,
options,
)
if unmatched:
rich_click_echo(
f"[warning]Unmatched template field: {unmatched}[/]"
)
for rendered_template in rendered_templates:
if not rendered_template:
continue
print(rendered_template)

View File

@@ -11,7 +11,7 @@ import shlex
import subprocess
import sys
import time
from typing import Dict
from typing import Iterable, List, Tuple
import click
import osxmetadata
@@ -364,6 +364,13 @@ from .verbose import get_verbose_console, time_stamp, verbose_print
is_flag=True,
help="Merge any persons found in the original file with persons used for '--exiftool' and '--sidecar'.",
)
@click.option(
"--favorite-rating",
is_flag=True,
help="When used with --exiftool or --sidecar, "
"set XMP:Rating=5 for photos marked as Favorite and XMP:Rating=0 for non-Favorites. "
"If not specified, XMP:Rating is not set.",
)
@click.option(
"--ignore-date-modified",
is_flag=True,
@@ -539,6 +546,26 @@ from .verbose import get_verbose_console, time_stamp, verbose_print
"for example, your own scripts or other files. Be sure this is what you intend before using "
"--cleanup. Use --dry-run with --cleanup first if you're not certain.",
)
@click.option(
"--keep",
metavar="KEEP_PATH",
nargs=1,
multiple=True,
help="When used with --cleanup, prevents file or directory KEEP_PATH from being deleted "
"when cleanup is run. Use this if there are files in the export directory that you don't "
"want to be deleted when --cleanup is run. "
"KEEP_PATH may be a file path, e.g. '/Volumes/Photos/keep.jpg', "
"or a file path and wild card, e.g. '/Volumes/Photos/*.txt', "
"or a directory, e.g. '/Volumes/Photos/KeepMe'. "
"KEEP_PATH may be an absolute path or a relative path. "
"If it is relative, it must be relative to the export destination. "
"For example if export destination is `/Volumes/Photos` and you want to keep all `.txt` files, "
'you can specify `--keep "/Volumes/Photos/*.txt"` or `--keep "*.txt"`. '
"If wild card is used, KEEP_PATH must be enclosed in quotes to prevent the shell from expanding the wildcard, "
'e.g. `--keep "/Volumes/Photos/*.txt"`. '
"If KEEP_PATH is a directory, all files and directories contained in KEEP_PATH will be kept. "
"--keep may be repeated to keep additional files/directories.",
)
@click.option(
"--add-exported-to-album",
metavar="ALBUM",
@@ -647,6 +674,17 @@ from .verbose import get_verbose_console, time_stamp, verbose_print
is_flag=True,
help="If specified, saves the config file but does not export any files; must be used with --save-config.",
)
@click.option(
"--print",
"print_template",
metavar="TEMPLATE",
multiple=True,
help="Render TEMPLATE string for each photo being exported and print to stdout. "
"TEMPLATE is an osxphotos template string. "
"This may be useful for creating custom reports, etc. "
"TEMPLATE will be printed after the photo is exported or skipped. "
"May be repeated to print multiple template strings. ",
)
@click.option(
"--beta",
is_flag=True,
@@ -730,6 +768,7 @@ def export(
exportdb,
external_edit,
favorite,
favorite_rating,
filename_template,
finder_tag_keywords,
finder_tag_template,
@@ -749,6 +788,7 @@ def export(
is_reference,
jpeg_ext,
jpeg_quality,
keep,
keyword_template,
keyword,
label,
@@ -776,6 +816,7 @@ def export(
not_live,
not_panorama,
not_portrait,
not_reference,
not_screenshot,
not_selfie,
not_shared,
@@ -796,6 +837,7 @@ def export(
preview_if_missing,
preview_suffix,
preview,
print_template,
profile_sort,
profile,
query_eval,
@@ -949,6 +991,7 @@ def export(
exportdb = cfg.exportdb
external_edit = cfg.external_edit
favorite = cfg.favorite
favorite_rating = cfg.favorite_rating
filename_template = cfg.filename_template
finder_tag_keywords = cfg.finder_tag_keywords
finder_tag_template = cfg.finder_tag_template
@@ -965,8 +1008,10 @@ def export(
ignore_date_modified = cfg.ignore_date_modified
ignore_signature = cfg.ignore_signature
in_album = cfg.in_album
is_reference = cfg.is_reference
jpeg_ext = cfg.jpeg_ext
jpeg_quality = cfg.jpeg_quality
keep = cfg.keep
keyword = cfg.keyword
keyword_template = cfg.keyword_template
label = cfg.label
@@ -993,6 +1038,7 @@ def export(
not_live = cfg.not_live
not_panorama = cfg.not_panorama
not_portrait = cfg.not_portrait
not_reference = cfg.not_reference
not_screenshot = cfg.not_screenshot
not_selfie = cfg.not_selfie
not_shared = cfg.not_shared
@@ -1014,6 +1060,7 @@ def export(
preview = cfg.preview
preview_if_missing = cfg.preview_if_missing
preview_suffix = cfg.preview_suffix
print_template = cfg.print_template
query_eval = cfg.query_eval
query_function = cfg.query_function
ramdb = cfg.ramdb
@@ -1098,13 +1145,16 @@ def export(
("slow_mo", "not_slow_mo"),
("time_lapse", "not_time_lapse"),
("title", "no_title"),
("is_reference", "not_reference"),
]
dependent_options = [
("exiftool_merge_keywords", ("exiftool", "sidecar")),
("exiftool_merge_persons", ("exiftool", "sidecar")),
("favorite_rating", ("exiftool", "sidecar")),
("exiftool_option", ("exiftool")),
("ignore_signature", ("update", "force_update")),
("jpeg_quality", ("convert_to_jpeg")),
("keep", ("cleanup")),
("missing", ("download_missing", "use_photos_export")),
("only_new", ("update", "force_update")),
("append", ("report")),
@@ -1358,6 +1408,7 @@ def export(
not_missing=None,
not_panorama=not_panorama,
not_portrait=not_portrait,
not_reference=not_reference,
not_screenshot=not_screenshot,
not_selfie=not_selfie,
not_shared=not_shared,
@@ -1470,6 +1521,7 @@ def export(
export_live=export_live,
export_preview=preview,
export_raw=export_raw,
favorite_rating=favorite_rating,
filename_template=filename_template,
fileutil=fileutil,
force_update=force_update,
@@ -1613,6 +1665,22 @@ def export(
report_writer.write(export_results)
if print_template:
options = RenderOptions(export_dir=dest)
for template in print_template:
rendered_templates, unmatched = p.render_template(
template,
options,
)
if unmatched:
rich_click_echo(
f"[warning]Unmatched template field: {unmatched}[/]"
)
for rendered_template in rendered_templates:
if not rendered_template:
continue
rich_click_echo(rendered_template)
progress.advance(task)
# handle limit
@@ -1678,9 +1746,18 @@ def export(
+ [r[0] for r in results.error]
+ db_files
)
# if --report, add report file to keep list to prevent it from being deleted
if report:
all_files.append(report)
dirs_to_keep = []
if keep:
files_to_keep, dirs_to_keep = collect_files_to_keep(keep, dest)
all_files += files_to_keep
rich_echo(f"Cleaning up [filepath]{dest}")
cleaned_files, cleaned_dirs = cleanup_files(
dest, all_files, fileutil, verbose_=verbose_
dest, all_files, dirs_to_keep, fileutil, verbose_=verbose_
)
file_str = "files" if len(cleaned_files) != 1 else "file"
dir_str = "directories" if len(cleaned_dirs) != 1 else "directory"
@@ -1725,6 +1802,7 @@ def export_photo(
exiftool_merge_keywords=False,
exiftool_merge_persons=False,
directory=None,
favorite_rating=False,
filename_template=None,
export_raw=None,
album_keyword=None,
@@ -1778,6 +1856,7 @@ def export_photo(
export_live: bool; also export live video component if photo is a live photo; live video will have same name as photo but with .mov extension
export_preview: export the preview image generated by Photos
export_raw: bool; if True exports raw image associate with the photo
favorite_rating: bool; if True, set XMP:Rating=5 for favorite images and XMP:Rating=0 for non-favorites
filename_template: template use to determine output file
fileutil: file util class compatible with FileUtilABC
force_update: bool, only export updated photos but trigger export even if only metadata has changed
@@ -1943,6 +2022,7 @@ def export_photo(
export_original=export_original,
export_preview=export_preview,
export_raw=export_raw,
favorite_rating=favorite_rating,
filename=original_filename,
fileutil=fileutil,
force_update=force_update,
@@ -2057,6 +2137,7 @@ def export_photo(
export_original=False,
export_preview=not export_original and export_preview,
export_raw=not export_original and export_raw,
favorite_rating=favorite_rating,
filename=edited_filename,
fileutil=fileutil,
force_update=force_update,
@@ -2142,6 +2223,7 @@ def export_photo_to_directory(
export_original,
export_preview,
export_raw,
favorite_rating,
filename,
fileutil,
force_update,
@@ -2203,6 +2285,7 @@ def export_photo_to_directory(
exiftool=exiftool,
export_as_hardlink=export_as_hardlink,
export_db=export_db,
favorite_rating=favorite_rating,
fileutil=fileutil,
force_update=force_update,
ignore_date_modified=ignore_date_modified,
@@ -2436,7 +2519,6 @@ def find_files_in_branch(pathname, filename):
# walk down the tree
for root, _, filenames in os.walk(pathname):
# for directory in directories:
# print(os.path.join(root, directory))
for fname in filenames:
if fname == filename and pathlib.Path(root) != pathname:
files.append(os.path.join(root, fname))
@@ -2453,14 +2535,43 @@ def find_files_in_branch(pathname, filename):
return files
def cleanup_files(dest_path, files_to_keep, fileutil, verbose_):
def collect_files_to_keep(
keep: Iterable[str], export_dir: str
) -> Tuple[List[str], List[str]]:
"""Collect all files to keep for --keep/--cleanup.
Args:
keep: Iterable of filepaths to keep; each path may be a filepath, a filepath/wildcard, or a directory path.
export_dir: the export directory which will be used to resolve paths when paths in keep are relative instead of absolute
Returns:
tuple of [files_to_keep], [dirs_to_keep]
"""
export_dir = pathlib.Path(export_dir)
keepers = []
for k in keep:
keeper = pathlib.Path(k).expanduser()
if not keeper.is_absolute():
# relative path: relative to export_dir
keeper = export_dir / keeper
if keeper.is_dir():
keepers.extend(keeper.glob("**/*"))
keepers.extend(keeper.parent.glob(keeper.name))
files_to_keep = [str(k) for k in keepers if k.is_file()]
dirs_to_keep = [str(k) for k in keepers if k.is_dir()]
return files_to_keep, dirs_to_keep
def cleanup_files(dest_path, files_to_keep, dirs_to_keep, fileutil, verbose_):
"""cleanup dest_path by deleting and files and empty directories
not in files_to_keep
Args:
dest_path: path to directory to clean
files_to_keep: list of full file paths to keep (not delete)
fileutile: FileUtil object
dirs_to_keep: list of full dir paths to keep (not delete if they are empty)
fileutil: FileUtil object
verbose_: verbose callable for printing verbose output
Returns:
tuple of (list of files deleted, list of directories deleted)
@@ -2480,6 +2591,8 @@ def cleanup_files(dest_path, files_to_keep, fileutil, verbose_):
deleted_dirs = []
# walk directory tree bottom up and verify contents are empty
for dirpath, _, _ in os.walk(dest_path, topdown=False):
if dirpath in dirs_to_keep:
continue
if not list(pathlib.Path(dirpath).glob("*")):
# directory and directory is empty
verbose_(f"Deleting empty directory {dirpath}")

1352
osxphotos/cli/import_cli.py Normal file

File diff suppressed because it is too large Load Diff

156
osxphotos/cli/orphans.py Normal file
View File

@@ -0,0 +1,156 @@
"""Find orphaned photos in a Photos library"""
import os
import os.path
import re
import sys
# using os.path.join is slightly slower inside loop than directly using the method
from os.path import join as joinpath
from os.path import splitext
from pathlib import Path
from typing import Dict
import click
from osxphotos import PhotosDB
from osxphotos._constants import _PHOTOS_4_VERSION
from osxphotos.fileutil import FileUtil
from osxphotos.utils import increment_filename, pluralize
from .click_rich_echo import rich_click_echo as echo
from .click_rich_echo import set_rich_console, set_rich_theme, set_rich_timestamp
from .color_themes import get_theme
from .common import DB_OPTION, THEME_OPTION, get_photos_db
from .help import get_help_msg
from .list import _list_libraries
from .verbose import get_verbose_console, verbose_print
@click.command(name="orphans")
@click.option(
"--export",
metavar="EXPORT_PATH",
required=False,
type=click.Path(file_okay=False, writable=True, resolve_path=True, exists=True),
help="Export orphans to directory EXPORT_PATH. If --export not specified, orphans are listed but not exported.",
)
@DB_OPTION
@click.option("--verbose", "-V", "verbose", is_flag=True, help="Print verbose output.")
@click.option("--timestamp", is_flag=True, help="Add time stamp to verbose output")
@THEME_OPTION
@click.pass_obj
@click.pass_context
def orphans(ctx, cli_obj, export, db, verbose, timestamp, theme):
"""Find orphaned photos in a Photos library"""
color_theme = get_theme(theme)
verbose_ = verbose_print(
verbose, timestamp, rich=True, theme=color_theme, highlight=False
)
# set console for rich_echo to be same as for verbose_
set_rich_console(get_verbose_console())
set_rich_theme(color_theme)
set_rich_timestamp(timestamp)
# below needed for to make CliRunner work for testing
cli_db = cli_obj.db if cli_obj is not None else None
db = get_photos_db(db, cli_db)
if not db:
echo(get_help_msg(orphans), err=True)
echo("\n\nLocated the following Photos library databases: ", err=True)
_list_libraries()
return
verbose_("Loading Photos database")
photosdb = PhotosDB(dbfile=db, verbose=verbose_, rich=True)
if photosdb.db_version <= _PHOTOS_4_VERSION:
echo(
"[error]Orphans can only be used with Photos libraries > version 5 (MacOS Catalina/10.15)[/]",
err=True,
)
sys.exit(1)
photos = photosdb.photos()
photos += photosdb.photos(intrash=True)
# need to add unselected bursts
burst_photos = [bp for p in photos for bp in p.burst_photos]
# will be some duplicates but those will be removed when converting to dict
photos.extend(burst_photos)
uuids_in_db = {photo.uuid: photo for photo in photos}
# walk the Photos library looking for photos associated with a uuid
uuids_in_library = {}
verbose_("Scanning for orphan files")
# originals
verbose_("Scanning original files")
directory = joinpath(photosdb.library_path, "originals")
scan_for_files(directory, uuids_in_library)
# edited
verbose_("Scanning edited files")
directory = joinpath(photosdb.library_path, "resources", "renders")
scan_for_files(directory, uuids_in_library)
# derivatives
verbose_("Scanning derivative files")
directory = joinpath(photosdb.library_path, "resources", "derivatives")
scan_for_files(directory, uuids_in_library)
# shared iCloud photos
verbose_("Scanning shared iCloud photos")
directory = joinpath(photosdb.library_path, "resources", "cloudsharing", "data")
scan_for_files(directory, uuids_in_library)
# shared derivatives
directory = joinpath(
"resources", "cloudsharing", "resources", "derivatives", "masters"
)
scan_for_files(directory, uuids_in_library)
# find orphans
possible_orphans = []
for uuid, files in uuids_in_library.items():
if uuid not in uuids_in_db:
possible_orphans.extend(files)
echo(
f"Found [num]{len(possible_orphans)}[/] "
f"{pluralize(len(possible_orphans), 'orphan', 'orphans')}"
)
exported = []
for orphan in possible_orphans:
echo(f"[filepath]{orphan}[/]")
if export:
dest = increment_filename(Path(export) / Path(orphan).name)
verbose_(f"Copying [filepath]{Path(orphan).name}[/] to [filepath]{dest}[/]")
FileUtil.copy(orphan, dest)
exported.append(dest)
if export:
echo(
f"Exported [num]{len(exported)}[/] "
f"{pluralize(len(exported), 'file', 'files')}"
)
def scan_for_files(directory: str, uuid_dict: Dict):
"""Walk a directory path finding any files named with UUID in the filename and add to uuid_dict
Note: modifies uuid_dict
"""
uuid_pattern = r"([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})"
uuid_regex = re.compile(uuid_pattern)
for dirpath, dirname, filenames in os.walk(directory):
for filename in filenames:
if match := uuid_regex.match(filename):
stem, ext = splitext(filename)
# .plist and .aae files may hold data on adjustments but these
# are not useful by themselves so skip them
if ext.lower() in [".plist", ".aae"]:
continue
filepath = joinpath(dirpath, filename)
try:
uuid_dict[match[0]].append(filepath)
except KeyError:
uuid_dict[match[0]] = [filepath]

View File

@@ -52,9 +52,16 @@ def trim(text: str, pad: str = "") -> str:
return text if len(text) <= width else f"{text[: width- 3]}..."
def inspect_photo(photo: PhotoInfo, detected_text: Optional[str] = None) -> str:
def inspect_photo(
photo: PhotoInfo,
detected_text: Optional[str] = None,
templates: Optional[List[str]] = None,
) -> str:
"""Get info about an osxphotos PhotoInfo object formatted for printing"""
if templates:
return inspect_photo_templates(photo, templates)
properties = [
bold("Filename: ") + f"[filename]{photo.original_filename}[/]",
bold("Type: ") + get_photo_type(photo),
@@ -78,14 +85,32 @@ def inspect_photo(photo: PhotoInfo, detected_text: Optional[str] = None) -> str:
+ f"[time]{photo.import_info.creation_date.isoformat()}[/]"
)
file_size = (
bold("File size: ")
+ f"[num]{float(bitmath.Byte(photo.original_filesize).to_MB()):.2f} MB[/]"
)
if photo.live_photo and photo.path_live_photo:
file_size += (
" | [num]"
+ f"{float(bitmath.Byte(pathlib.Path(photo.path_live_photo).stat().st_size).to_MB()):.2f}"
+ " MB (Live video)[/]"
)
if photo.has_raw and photo.path_raw:
file_size += (
" | [num]"
+ f"{float(bitmath.Byte(pathlib.Path(photo.path_raw).stat().st_size).to_MB()):.2f}"
+ " MB (RAW photo)[/]"
)
properties.extend(
[
bold("Dimensions: ")
+ f"[num]{photo.width}[/] x [num]{photo.height}[/] "
+ bold("Orientation: ")
+ f"[num]{photo.orientation}[/]",
bold("File size: ")
+ f"[num]{float(bitmath.Byte(photo.original_filesize).to_MB()):.2f} MB[/]",
file_size,
bold("Title: ") + f"{photo.title or '-'}",
bold("Description: ")
+ f"{trim(photo.description or '-', 'Description: ')}",
@@ -99,7 +124,7 @@ def inspect_photo(photo: PhotoInfo, detected_text: Optional[str] = None) -> str:
bold("Location: ")
+ f"{', '.join(dd_to_dms_str(*photo.location)) if photo.location[0] else '-'}",
bold("Place: ") + f"{photo.place.name if photo.place else '-'}",
bold("Categories: ") + f"{', '.join(photo.labels) or '-'}",
bold("Categories/Labels: ") + f"{', '.join(photo.labels) or '-'}",
]
)
properties.append(format_flags(photo))
@@ -140,6 +165,30 @@ def inspect_photo(photo: PhotoInfo, detected_text: Optional[str] = None) -> str:
return "\n".join(properties)
def inspect_photo_templates(
photo: PhotoInfo, templates: Optional[List[str]] = None
) -> str:
"""Render and display photo templates"""
properties = [
bold("Filename: ") + f"[filename]{photo.original_filename}[/]",
bold("Type: ") + get_photo_type(photo),
bold("UUID: ") + f"[uuid]{photo.uuid}[/]",
]
properties.append(bold("Templates: "))
properties.append(format_templates(photo, templates))
return "\n".join(properties)
def format_templates(photo: PhotoInfo, templates: List[str]) -> str:
"""Format templates for a photo"""
formatted_templates = []
for template in templates:
template_str, _ = photo.render_template(template)
formatted_templates.append((template, template_str))
return "\n".join(f"{t[0]} = {t[1]}" for t in formatted_templates)
def format_score_info(photo: PhotoInfo) -> str:
"""Format score_info"""
score_str = bold("Score: ")
@@ -200,6 +249,10 @@ def format_paths(photo: PhotoInfo) -> str:
path_str += "\n"
path_str += bold("Path edited: ")
path_str += f"[filepath]{format_path_link(photo.path_edited)}[/]"
if photo.path_live_photo:
path_str += "\n"
path_str += bold("Path live video: ")
path_str += f"[filepath]{format_path_link(photo.path_live_photo)}[/]"
if photo.path_raw:
path_str += "\n"
path_str += bold("Path raw: ")
@@ -339,9 +392,18 @@ def make_layout() -> Layout:
@click.command(name="inspect")
@click.option("--detect-text", "-t", is_flag=True, help="Detect text in photos")
@click.option(
"--template",
"-T",
metavar="TEMPLATE",
multiple=True,
help="Template string to render for each photo using template preview mode. "
"Useful for testing templates for export; may be repeated to test multiple templates. "
"If --template/-T is used, other inspection data will not be displayed. ",
)
@THEME_OPTION
@DB_OPTION
def photo_inspect(db, theme, detect_text):
def photo_inspect(db, theme, detect_text, template):
"""Interactively inspect photos selected in Photos.
Open Photos then run `osxphotos inspect` in the terminal.
@@ -375,7 +437,7 @@ def photo_inspect(db, theme, detect_text):
if uuid == CURRENT_UUID:
layout["main"].update(
Panel(
inspect_photo(photo, text),
inspect_photo(photo, detected_text=text),
title=photo.title or photo.original_filename,
)
)
@@ -414,14 +476,16 @@ def photo_inspect(db, theme, detect_text):
inspect_photo(
photo,
detected_text=detected_text_cache.get(uuid, None),
templates=template,
),
title=photo.title or photo.original_filename,
)
)
# start text detection if requested
# start text detection if requested (but not if in template preview mode)
if (
detect_text
and not template
and photo.isphoto
and (
photo.path

View File

@@ -1,8 +1,9 @@
"""print_photo_info function to print PhotoInfo objects"""
import csv
import json
import sys
from typing import Callable, List
from typing import Callable, List, Tuple
from osxphotos.photoinfo import PhotoInfo
@@ -110,3 +111,44 @@ def print_photo_info(
)
for row in dump:
csv_writer.writerow(row)
def print_photo_fields(
photos: List[PhotoInfo], fields: Tuple[Tuple[str]], json_format: bool
):
"""Output custom field templates from PhotoInfo objects
Args:
photos: List of PhotoInfo objects
fields: Tuple of Tuple of field names/field templates to output"""
keys = [f[0] for f in fields]
data = []
for p in photos:
record = {}
for field in fields:
rendered_value, unmatched = p.render_template(field[1])
if unmatched:
raise ValueError(
f"Unmatched template variables in field {field[0]}: {field[1]}"
)
field_value = (
rendered_value[0]
if len(rendered_value) == 1
else ",".join(rendered_value)
if not json_format
else rendered_value
)
record[field[0]] = field_value
data.append(record)
if json_format:
print(json.dumps(data, indent=4))
else:
# dump as CSV
csv_writer = csv.writer(
sys.stdout, delimiter=",", quotechar='"', quoting=csv.QUOTE_MINIMAL
)
# add headers
csv_writer.writerow(keys)
for record in data:
csv_writer.writerow(record.values())

View File

@@ -3,16 +3,24 @@
import click
import osxphotos
from osxphotos.cli.click_rich_echo import (
rich_click_echo,
set_rich_console,
set_rich_theme,
)
from osxphotos.debug import set_debug
from osxphotos.photosalbum import PhotosAlbum
from osxphotos.phototemplate import RenderOptions
from osxphotos.queryoptions import QueryOptions
from .color_themes import get_default_theme
from .common import (
CLI_COLOR_ERROR,
CLI_COLOR_WARNING,
DB_ARGUMENT,
DB_OPTION,
DELETED_OPTIONS,
FIELD_OPTION,
JSON_OPTION,
OSXPHOTOS_HIDDEN,
QUERY_OPTIONS,
@@ -20,7 +28,8 @@ from .common import (
load_uuid_from_file,
)
from .list import _list_libraries
from .print_photo_info import print_photo_info
from .print_photo_info import print_photo_fields, print_photo_info
from .verbose import get_verbose_console
@click.command()
@@ -62,6 +71,24 @@ from .print_photo_info import print_photo_info
"This only works if the Photos library being queried is the last-opened (default) library in Photos. "
"This feature is currently experimental. I don't know how well it will work on large query sets.",
)
@click.option(
"--quiet",
is_flag=True,
help="Quiet output; doesn't actually print query results. "
"Useful with --print and --add-to-album if you don't want to see the actual query results.",
)
@FIELD_OPTION
@click.option(
"--print",
"print_template",
metavar="TEMPLATE",
multiple=True,
help="Render TEMPLATE string for each photo queried and print to stdout. "
"TEMPLATE is an osxphotos template string. "
"This may be useful for creating custom reports, etc. "
"Most useful with --quiet. "
"May be repeated to print multiple template strings. ",
)
@click.option(
"--debug", required=False, is_flag=True, default=False, hidden=OSXPHOTOS_HIDDEN
)
@@ -88,6 +115,7 @@ def query(
exif,
external_edit,
favorite,
field,
folder,
from_date,
from_time,
@@ -127,6 +155,7 @@ def query(
not_missing,
not_panorama,
not_portrait,
not_reference,
not_screenshot,
not_selfie,
not_shared,
@@ -138,8 +167,10 @@ def query(
person,
place,
portrait,
print_template,
query_eval,
query_function,
quiet,
regex,
screenshot,
selected,
@@ -176,7 +207,6 @@ def query(
from_date,
from_time,
has_raw,
is_reference,
keyword,
label,
max_size,
@@ -220,6 +250,7 @@ def query(
(shared, not_shared),
(slow_mo, not_slow_mo),
(time_lapse, not_time_lapse),
(is_reference, not_reference),
]
# 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(
@@ -229,6 +260,10 @@ def query(
click.echo(ctx.obj.group.commands["query"].get_help(ctx), err=True)
return
# set console for rich_echo to be same as for verbose_
set_rich_console(get_verbose_console())
set_rich_theme(get_default_theme())
# actually have something to query
# default searches for everything
photos = True
@@ -309,6 +344,7 @@ def query(
not_missing=not_missing,
not_panorama=not_panorama,
not_portrait=not_portrait,
not_reference=not_reference,
not_screenshot=not_screenshot,
not_selfie=not_selfie,
not_shared=not_shared,
@@ -366,4 +402,25 @@ def query(
err=True,
)
print_photo_info(photos, cli_json or json_, print_func=click.echo)
if field:
print_photo_fields(photos, field, cli_json or json_)
if print_template:
options = RenderOptions()
for p in photos:
for template in print_template:
rendered_templates, unmatched = p.render_template(
template,
options,
)
if unmatched:
rich_click_echo(
f"[warning]Unmatched template field: {unmatched}[/]"
)
for rendered_template in rendered_templates:
if not rendered_template:
continue
print(rendered_template)
if not quiet and not field:
print_photo_info(photos, cli_json or json_, print_func=click.echo)

View File

@@ -253,7 +253,6 @@ def _query_options_from_kwargs(**kwargs) -> QueryOptions:
"from_date",
"from_time",
"has_raw",
"is_reference",
"keyword",
"label",
"max_size",
@@ -294,6 +293,7 @@ def _query_options_from_kwargs(**kwargs) -> QueryOptions:
("shared", "not_shared"),
("slow_mo", "not_slow_mo"),
("time_lapse", "not_time_lapse"),
("is_reference", "not_reference"),
]
# print help if no non-exclusive term or a double exclusive term is given
# TODO: add option to validate requiring at least one query arg

View File

@@ -2,16 +2,18 @@
import csv
import datetime
import json
import os
import os.path
import sqlite3
from abc import ABC, abstractmethod
from contextlib import suppress
from typing import Union, Dict
from typing import Dict, Union
from osxphotos.photoexporter import ExportResults
from osxphotos.export_db import OSXPHOTOS_ABOUT_STRING
from osxphotos.photoexporter import ExportResults
from osxphotos.sqlite_utils import sqlite_columns
__all__ = [
"report_writer_factory",
@@ -166,18 +168,20 @@ class ReportWriterSQLite(ReportWriterABC):
self._conn = sqlite3.connect(self.output_file)
self._create_tables()
self.report_id = self._generate_report_id()
def write(self, export_results: ExportResults):
"""Write results to the output file"""
all_results = prepare_results_for_writing(export_results)
for data in list(all_results.values()):
data["report_id"] = self.report_id
cursor = self._conn.cursor()
cursor.execute(
"INSERT INTO report "
"(datetime, filename, exported, new, updated, skipped, exif_updated, touched, converted_to_jpeg, sidecar_xmp, sidecar_json, sidecar_exiftool, missing, error, exiftool_warning, exiftool_error, extended_attributes_written, extended_attributes_skipped, cleanup_deleted_file, cleanup_deleted_directory, exported_album) "
"(datetime, filename, exported, new, updated, skipped, exif_updated, touched, converted_to_jpeg, sidecar_xmp, sidecar_json, sidecar_exiftool, missing, error, exiftool_warning, exiftool_error, extended_attributes_written, extended_attributes_skipped, cleanup_deleted_file, cleanup_deleted_directory, exported_album, report_id) "
"VALUES "
"(:datetime, :filename, :exported, :new, :updated, :skipped, :exif_updated, :touched, :converted_to_jpeg, :sidecar_xmp, :sidecar_json, :sidecar_exiftool, :missing, :error, :exiftool_warning, :exiftool_error, :extended_attributes_written, :extended_attributes_skipped, :cleanup_deleted_file, :cleanup_deleted_directory, :exported_album);",
"(:datetime, :filename, :exported, :new, :updated, :skipped, :exif_updated, :touched, :converted_to_jpeg, :sidecar_xmp, :sidecar_json, :sidecar_exiftool, :missing, :error, :exiftool_warning, :exiftool_error, :extended_attributes_written, :extended_attributes_skipped, :cleanup_deleted_file, :cleanup_deleted_directory, :exported_album, :report_id);",
data,
)
self._conn.commit()
@@ -191,27 +195,27 @@ class ReportWriterSQLite(ReportWriterABC):
c.execute(
"""
CREATE TABLE IF NOT EXISTS report (
datetime text,
filename text,
exported integer,
new integer,
updated integer,
skipped integer,
exif_updated integer,
touched integer,
converted_to_jpeg integer,
sidecar_xmp integer,
sidecar_json integer,
sidecar_exiftool integer,
missing integer,
error text,
exiftool_warning text,
exiftool_error text,
extended_attributes_written integer,
extended_attributes_skipped integer,
cleanup_deleted_file integer,
cleanup_deleted_directory integer,
exported_album text
datetime TEXT,
filename TEXT,
exported INTEGER,
new INTEGER,
updated INTEGER,
skipped INTEGER,
exif_updated INTEGER,
touched INTEGER,
converted_to_jpeg INTEGER,
sidecar_xmp INTEGER,
sidecar_json INTEGER,
sidecar_exiftool INTEGER,
missing INTEGER,
error TEXT,
exiftool_warning TEXT,
exiftool_error TEXT,
extended_attributes_written INTEGER,
extended_attributes_skipped INTEGER,
cleanup_deleted_file INTEGER,
cleanup_deleted_directory INTEGER,
exported_album TEXT
)
"""
)
@@ -226,9 +230,55 @@ class ReportWriterSQLite(ReportWriterABC):
"INSERT INTO about(about) VALUES (?);",
(f"OSXPhotos Export Report. {OSXPHOTOS_ABOUT_STRING}",),
)
c.execute(
"""
CREATE TABLE IF NOT EXISTS report_id (
report_id INTEGER PRIMARY KEY,
datetime TEXT
);"""
)
self._conn.commit()
# migrate report table to add report_id if needed (#731)
if "report_id" not in sqlite_columns(self._conn, "report"):
self._conn.cursor().execute("ALTER TABLE report ADD COLUMN report_id TEXT;")
self._conn.commit()
# create report_summary view
c.execute(
"""
CREATE VIEW IF NOT EXISTS report_summary AS
SELECT
report_id,
datetime(MIN(datetime)) start_time,
datetime(MAX(datetime)) end_time,
STRFTIME('%s',MAX(datetime)) - STRFTIME('%s',MIN(datetime)) AS duration_s,
SUM(exported) AS exported,
sum(new) as new,
SUM(updated) as updated,
SUM(skipped) as skipped,
SUM(sidecar_xmp) as sidecar_xmp,
SUM(touched) as touched,
SUM(converted_to_jpeg) as converted_to_jpeg,
SUM(missing) as missing,
SUM(CASE WHEN error = "" THEN 0 ELSE 1 END) as error,
SUM(cleanup_deleted_file) as cleanup_deleted_file
FROM report
GROUP BY report_id;"""
)
self._conn.commit()
def _generate_report_id(self) -> int:
"""Get a new report ID for this report"""
c = self._conn.cursor()
c.execute(
"INSERT INTO report_id(datetime) VALUES (?);",
(datetime.datetime.now().isoformat(),),
)
report_id = c.lastrowid
self._conn.commit()
return report_id
def __del__(self):
with suppress(Exception):
self.close()

Binary file not shown.

View File

@@ -6,7 +6,9 @@
If these aren't important to you, I highly recommend you use Sven Marnach's excellent
pyexiftool: https://github.com/smarnach/pyexiftool which provides more functionality """
import atexit
import contextlib
import html
import json
import logging
@@ -104,9 +106,12 @@ class _ExifToolProc:
return cls.instance
def __init__(self, exiftool=None):
def __init__(self, exiftool=None, large_file_support=True):
"""construct _ExifToolProc singleton object or return instance of already created object
exiftool: optional path to exiftool binary (if not provided, will search path to find it)
Args:
exiftool: optional path to exiftool binary (if not provided, will search path to find it)
large_file_support: if True, enables large file support (>4GB) via `-api largefilesupport=1`
"""
if hasattr(self, "_process_running") and self._process_running:
@@ -119,16 +124,14 @@ class _ExifToolProc:
return
self._process_running = False
self._exiftool = exiftool or get_exiftool_path()
self._start_proc()
self._start_proc(large_file_support=large_file_support)
@property
def process(self):
"""return the exiftool subprocess"""
if self._process_running:
return self._process
else:
if not self._process_running:
self._start_proc()
return self._process
return self._process
@property
def pid(self):
@@ -140,7 +143,7 @@ class _ExifToolProc:
"""return path to exiftool process"""
return self._exiftool
def _start_proc(self):
def _start_proc(self, large_file_support):
"""start exiftool in batch mode"""
if self._process_running:
@@ -151,11 +154,13 @@ class _ExifToolProc:
# make sure /usr/bin at start of path so exiftool can find xattr (see #636)
env = os.environ.copy()
env["PATH"] = f'/usr/bin/:{env["PATH"]}'
large_file_args = ["-api", "largefilesupport=1"] if large_file_support else []
self._process = subprocess.Popen(
[
self._exiftool,
"-stay_open", # keep process open in batch mode
"True", # -stay_open=True, keep process open in batch mode
*large_file_args,
"-@", # read command-line arguments from file
"-", # read from stdin
"-common_args", # specifies args common to all commands subsequently run
@@ -179,13 +184,10 @@ class _ExifToolProc:
if not self._process_running:
return
try:
with contextlib.suppress(Exception):
self._process.stdin.write(b"-stay_open\n")
self._process.stdin.write(b"False\n")
self._process.stdin.flush()
except Exception as e:
pass
try:
self._process.communicate(timeout=5)
except subprocess.TimeoutExpired:
@@ -199,7 +201,14 @@ class _ExifToolProc:
class ExifTool:
"""Basic exiftool interface for reading and writing EXIF tags"""
def __init__(self, filepath, exiftool=None, overwrite=True, flags=None):
def __init__(
self,
filepath,
exiftool=None,
overwrite=True,
flags=None,
large_file_support=True,
):
"""Create ExifTool object
Args:
@@ -207,6 +216,7 @@ class ExifTool:
exiftool: path to exiftool, if not specified will look in path
overwrite: if True, will overwrite image file without creating backup, default=False
flags: optional list of exiftool flags to prepend to exiftool command when writing metadata (e.g. -m or -F)
large_file_support: if True, enables large file support in exiftool (`-api largefilesupport=1`)
Returns:
ExifTool instance
@@ -219,7 +229,9 @@ class ExifTool:
self.error = None
# if running as a context manager, self._context_mgr will be True
self._context_mgr = False
self._exiftoolproc = _ExifToolProc(exiftool=exiftool)
self._exiftoolproc = _ExifToolProc(
exiftool=exiftool, large_file_support=large_file_support
)
self._read_exif()
@property
@@ -327,7 +339,7 @@ class ExifTool:
commands = list(commands)
commands.append("-overwrite_original")
filename = os.fsencode(self.file) if not no_file else b""
filename = b"" if no_file else os.fsencode(self.file)
if self.flags:
# need to split flags, e.g. so "--ext AVI" becomes ["--ext", "AVI"]
@@ -423,8 +435,7 @@ class ExifTool:
def _read_exif(self):
"""read exif data from file"""
data = self.asdict()
self.data = {k: v for k, v in data.items()}
self.data = self.asdict().copy()
def __str__(self):
return f"file: {self.file}\nexiftool: {self._exiftoolproc._exiftool}"

View File

@@ -92,11 +92,13 @@ class PersonInfo:
return f"PersonInfo(name={self.name}, display_name={self.display_name}, uuid={self.uuid}, facecount={self.facecount})"
def __eq__(self, other):
if not isinstance(other, type(self)):
return False
return all(
getattr(self, field) == getattr(other, field) for field in ["_db", "_pk"]
return (
all(
getattr(self, field) == getattr(other, field)
for field in ["_db", "_pk"]
)
if isinstance(other, type(self))
else False
)
def __ne__(self, other):
@@ -127,23 +129,14 @@ class FaceInfo:
self._person_pk = face["person"]
self.center_x = face["centerx"]
self.center_y = face["centery"]
self.mouth_x = face["mouthx"]
self.mouth_y = face["mouthy"]
self.left_eye_x = face["lefteyex"]
self.left_eye_y = face["lefteyey"]
self.right_eye_x = face["righteyex"]
self.right_eye_y = face["righteyey"]
self.size = face["size"]
self.quality = face["quality"]
self.source_width = face["sourcewidth"]
self.source_height = face["sourceheight"]
self.has_smile = face["has_smile"]
self.left_eye_closed = face["left_eye_closed"]
self.right_eye_closed = face["right_eye_closed"]
self.manual = face["manual"]
self.face_type = face["facetype"]
self.age_type = face["agetype"]
# self.bald_type = face["baldtype"]
self.eye_makeup_type = face["eyemakeuptype"]
self.eye_state = face["eyestate"]
self.facial_hair_type = face["facialhairtype"]
@@ -174,33 +167,6 @@ class FaceInfo:
size_reference = photo.width if photo.width > photo.height else photo.height
return self.size * size_reference
@property
def mouth(self):
"""Coordinates, in PIL format, for mouth position
Returns:
tuple of coordinates in form (x, y)
"""
return self._make_point_with_rotation((self.mouth_x, self.mouth_y))
@property
def left_eye(self):
"""Coordinates, in PIL format, for left eye position
Returns:
tuple of coordinates in form (x, y)
"""
return self._make_point_with_rotation((self.left_eye_x, self.left_eye_y))
@property
def right_eye(self):
"""Coordinates, in PIL format, for right eye position
Returns:
tuple of coordinates in form (x, y)
"""
return self._make_point_with_rotation((self.right_eye_x, self.right_eye_y))
@property
def person_info(self):
"""PersonInfo instance for person associated with this face"""
@@ -415,15 +381,6 @@ class FaceInfo:
"center_x": self.center_x,
"center_y": self.center_y,
"center": self.center,
"mouth_x": self.mouth_x,
"mouth_y": self.mouth_y,
"mouth": self.mouth,
"left_eye_x": self.left_eye_x,
"left_eye_y": self.left_eye_y,
"left_eye": self.left_eye,
"right_eye_x": self.right_eye_x,
"right_eye_y": self.right_eye_y,
"right_eye": self.right_eye,
"size": self.size,
"face_rect": self.face_rect(),
"mpri_reg_rect": self.mpri_reg_rect._asdict(),
@@ -435,12 +392,9 @@ class FaceInfo:
"source_width": self.source_width,
"source_height": self.source_height,
"has_smile": self.has_smile,
"left_eye_closed": self.left_eye_closed,
"right_eye_closed": self.right_eye_closed,
"manual": self.manual,
"face_type": self.face_type,
"age_type": self.age_type,
# "bald_type": self.bald_type,
"eye_makeup_type": self.eye_makeup_type,
"eye_state": self.eye_state,
"facial_hair_type": self.facial_hair_type,

View File

@@ -136,6 +136,7 @@ class ExportOptions:
use_photokit (bool, default=False): if True, will use photokit to export photos when use_photos_export is True
verbose (callable): optional callable function to use for printing verbose text during processing; if None (default), does not print output.
tmpdir: (str, default=None): Optional directory to use for temporary files, if None (default) uses system tmp directory
favorite_rating (bool): if True, set XMP:Rating=5 for favorite images and XMP:Rating=0 for non-favorites
"""
@@ -181,6 +182,7 @@ class ExportOptions:
use_photos_export: bool = False
verbose: t.Optional[t.Callable] = None
tmpdir: t.Optional[str] = None
favorite_rating: bool = False
def asdict(self):
return asdict(self)
@@ -949,7 +951,7 @@ class PhotoExporter:
# export live_photo .mov file?
live_photo = bool(options.live_photo and self.photo.live_photo)
overwrite = any([options.overwrite, options.update, options.force_update])
edited_version = options.edited or self.photo.shared
edited_version = bool(options.edited or self.photo.shared)
# shared photos (in shared albums) show up as not having adjustments (not edited)
# but Photos is unable to export the "original" as only a jpeg copy is shared in iCloud
# so tell Photos to export the current version in this case
@@ -1586,6 +1588,7 @@ class PhotoExporter:
QuickTime:ModifyDate (UTC)
QuickTime:GPSCoordinates
UserData:GPSCoordinates
XMP:Rating
Reference:
https://iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201610_1.pdf
@@ -1701,8 +1704,8 @@ class PhotoExporter:
if options.face_regions and self.photo.face_info:
exif.update(self._get_mwg_face_regions_exiftool())
# if self.favorite():
# exif["Rating"] = 5
if options.favorite_rating:
exif["XMP:Rating"] = 5 if self.photo.favorite else 0
if options.location:
(lat, lon) = self.photo.location
@@ -2009,6 +2012,11 @@ class PhotoExporter:
latlon = self.photo.location if options.location else (None, None)
if options.favorite_rating:
rating = 5 if self.photo.favorite else 0
else:
rating = None
xmp_str = xmp_template.render(
photo=self.photo,
description=description,
@@ -2018,6 +2026,7 @@ class PhotoExporter:
extension=extension,
location=latlon,
version=__version__,
rating=rating,
)
# remove extra lines that mako inserts from template

View File

@@ -4,7 +4,7 @@ from typing import List, Optional
import photoscript
from more_itertools import chunked
from photoscript import Photo, PhotosLibrary
from photoscript import Album, Folder, Photo, PhotosLibrary
from .photoinfo import PhotoInfo
from .utils import noop, pluralize
@@ -51,19 +51,72 @@ class PhotosAlbum:
return self.album.photos()
def folder_by_path(folders: List[str], verbose: Optional[callable] = None) -> Folder:
"""Get (and create if necessary) a Photos Folder by path (passed as list of folder names)"""
library = PhotosLibrary()
verbose = verbose or noop
top_folder_name = folders.pop(0)
top_folder = library.folder(top_folder_name, top_level=True)
if not top_folder:
verbose(f"Creating folder '{top_folder_name}'")
top_folder = library.create_folder(top_folder_name)
current_folder = top_folder
for folder_name in folders:
folder = current_folder.folder(folder_name)
if not folder:
verbose(f"Creating folder '{folder_name}'")
folder = current_folder.create_folder(folder_name)
current_folder = folder
return current_folder
def album_by_path(
folders_album: List[str], verbose: Optional[callable] = None
) -> Album:
"""Get (and create if necessary) a Photos Album by path (pass as list of folders, album name)"""
library = PhotosLibrary()
verbose = verbose or noop
if len(folders_album) > 1:
# have folders
album_name = folders_album.pop()
folder = folder_by_path(folders_album, verbose)
album = folder.album(album_name)
if not album:
verbose(f"Creating album '{album_name}'")
album = folder.create_album(album_name)
else:
# only have album name
album_name = folders_album[0]
album = library.album(album_name, top_level=True)
if not album:
verbose(f"Creating album '{album_name}'")
album = library.create_album(album_name)
return album
class PhotosAlbumPhotoScript:
"""Add photoscript.Photo objects to album"""
def __init__(self, name: str, verbose: Optional[callable] = None):
self.name = name
def __init__(
self, name: str, verbose: Optional[callable] = None, split_folder: Optional[str] = None
):
"""Return a PhotosAlbumPhotoScript object, creating the album if necessary
Args:
name: Name of album
verbose: optional callable to print verbose output
split_folder: if set, split album name on value of split_folder to create folders if necessary,
e.g. if name = 'folder1/folder2/album' and split_folder='/',
then folders 'folder1' and 'folder2' will be created and album 'album' will be created in 'folder2';
if not set, album 'folder1/folder2/album' will be created
"""
self.verbose = verbose or noop
self.library = PhotosLibrary()
album = self.library.album(name)
if album is None:
self.verbose(f"Creating Photos album '{self.name}'")
album = self.library.create_album(name)
self.album = album
folders_album = name.split(split_folder) if split_folder else [name]
self.album = album_by_path(folders_album, verbose=verbose)
self.name = name
def add(self, photo: Photo):
self.album.add([photo])

View File

@@ -6,7 +6,8 @@ import datetime
from dataclasses import dataclass
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION, TIME_DELTA
from ..utils import _open_sql_file, normalize_unicode
from ..utils import normalize_unicode
from ..sqlite_utils import sqlite_open_ro
def _process_comments(self):
@@ -65,7 +66,7 @@ def _process_comments_5(photosdb):
asset_table = _DB_TABLE_NAMES[photosdb._photos_ver]["ASSET"]
(conn, cursor) = _open_sql_file(db)
(conn, cursor) = sqlite_open_ro(db)
results = conn.execute(
"""

View File

@@ -4,7 +4,7 @@
import logging
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
from ..utils import _db_is_locked, _open_sql_file
from ..sqlite_utils import sqlite_open_ro, sqlite_db_is_locked
from .photosdb_utils import get_db_version
@@ -38,7 +38,7 @@ def _process_exifinfo_5(photosdb):
asset_table = _DB_TABLE_NAMES[photosdb._photos_ver]["ASSET"]
(conn, cursor) = _open_sql_file(db)
(conn, cursor) = sqlite_open_ro(db)
result = conn.execute(
f"""

View File

@@ -1,13 +1,12 @@
""" Methods for PhotosDB to add Photos face info
"""
import logging
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
from ..utils import _open_sql_file, normalize_unicode
from ..sqlite_utils import sqlite_open_ro
from ..utils import normalize_unicode
from .photosdb_utils import get_db_version
"""
This module should be imported in the class defintion of PhotosDB in photosdb.py
Do not import this module directly
@@ -42,7 +41,7 @@ def _process_faceinfo_4(photosdb):
"""
db = photosdb._tmp_db
(conn, cursor) = _open_sql_file(db)
(conn, cursor) = sqlite_open_ro(db)
result = cursor.execute(
"""
@@ -181,7 +180,7 @@ def _process_faceinfo_5(photosdb):
asset_table = _DB_TABLE_NAMES[photosdb._photos_ver]["ASSET"]
(conn, cursor) = _open_sql_file(db)
(conn, cursor) = sqlite_open_ro(db)
result = cursor.execute(
f"""
@@ -202,8 +201,8 @@ def _process_faceinfo_5(photosdb):
ZDETECTEDFACE.ZHASSMILE,
ZDETECTEDFACE.ZHIDDEN,
ZDETECTEDFACE.ZISINTRASH,
ZDETECTEDFACE.ZISLEFTEYECLOSED,
ZDETECTEDFACE.ZISRIGHTEYECLOSED,
NULL, -- ZDETECTEDFACE.ZISLEFTEYECLOSED
NULL, -- ZDETECTEDFACE.ZISRIGHTEYECLOSED
ZDETECTEDFACE.ZLIPMAKEUPTYPE,
ZDETECTEDFACE.ZMANUAL,
ZDETECTEDFACE.ZQUALITYMEASURE,
@@ -213,17 +212,17 @@ def _process_faceinfo_5(photosdb):
ZDETECTEDFACE.ZBLURSCORE,
ZDETECTEDFACE.ZCENTERX,
ZDETECTEDFACE.ZCENTERY,
ZDETECTEDFACE.ZLEFTEYEX,
ZDETECTEDFACE.ZLEFTEYEY,
ZDETECTEDFACE.ZMOUTHX,
ZDETECTEDFACE.ZMOUTHY,
NULL, -- ZDETECTEDFACE.ZLEFTEYEX,
NULL, -- ZDETECTEDFACE.ZLEFTEYEY,
NULL, -- ZDETECTEDFACE.ZMOUTHX,
NULL, -- ZDETECTEDFACE.ZMOUTHY,
ZDETECTEDFACE.ZPOSEYAW,
ZDETECTEDFACE.ZQUALITY,
ZDETECTEDFACE.ZRIGHTEYEX,
ZDETECTEDFACE.ZRIGHTEYEY,
NULL, -- ZDETECTEDFACE.ZRIGHTEYEX,
NULL, -- ZDETECTEDFACE.ZRIGHTEYEY,
ZDETECTEDFACE.ZROLL,
ZDETECTEDFACE.ZSIZE,
ZDETECTEDFACE.ZYAW,
NULL, -- ZDETECTEDFACE.ZYAW,
ZDETECTEDFACE.ZMASTERIDENTIFIER
FROM ZDETECTEDFACE
JOIN {asset_table} ON {asset_table}.Z_PK = ZDETECTEDFACE.ZASSET
@@ -237,7 +236,7 @@ def _process_faceinfo_5(photosdb):
# 3 ZDETECTEDFACE.ZPERSON,
# 4 ZPERSON.ZFULLNAME,
# 5 ZDETECTEDFACE.ZAGETYPE,
# 6 ZDETECTEDFACE.ZBALDTYPE, (Not available on Monterey)
# 6 NULL -- ZDETECTEDFACE.ZBALDTYPE, (Not available on Monterey)
# 7 ZDETECTEDFACE.ZEYEMAKEUPTYPE,
# 8 ZDETECTEDFACE.ZEYESSTATE,
# 9 ZDETECTEDFACE.ZFACIALHAIRTYPE,
@@ -247,8 +246,8 @@ def _process_faceinfo_5(photosdb):
# 13 ZDETECTEDFACE.ZHASSMILE,
# 14 ZDETECTEDFACE.ZHIDDEN,
# 15 ZDETECTEDFACE.ZISINTRASH,
# 16 ZDETECTEDFACE.ZISLEFTEYECLOSED,
# 17 ZDETECTEDFACE.ZISRIGHTEYECLOSED,
# 16 NULL -- ZDETECTEDFACE.ZISLEFTEYECLOSED,
# 17 NULL -- ZDETECTEDFACE.ZISRIGHTEYECLOSED,
# 18 ZDETECTEDFACE.ZLIPMAKEUPTYPE,
# 19 ZDETECTEDFACE.ZMANUAL,
# 20 ZDETECTEDFACE.ZQUALITYMEASURE,
@@ -258,17 +257,17 @@ def _process_faceinfo_5(photosdb):
# 24 ZDETECTEDFACE.ZBLURSCORE,
# 25 ZDETECTEDFACE.ZCENTERX,
# 26 ZDETECTEDFACE.ZCENTERY,
# 27 ZDETECTEDFACE.ZLEFTEYEX,
# 28 ZDETECTEDFACE.ZLEFTEYEY,
# 29 ZDETECTEDFACE.ZMOUTHX,
# 30 ZDETECTEDFACE.ZMOUTHY,
# 27 NULL -- ZDETECTEDFACE.ZLEFTEYEX, (Not available on Ventura)
# 28 NULL -- ZDETECTEDFACE.ZLEFTEYEY, (Not available on Ventura)
# 29 NULL -- ZDETECTEDFACE.ZMOUTHX, (Not available on Ventura)
# 30 NULL -- ZDETECTEDFACE.ZMOUTHY, (Not available on Ventura)
# 31 ZDETECTEDFACE.ZPOSEYAW,
# 32 ZDETECTEDFACE.ZQUALITY,
# 33 ZDETECTEDFACE.ZRIGHTEYEX,
# 34 ZDETECTEDFACE.ZRIGHTEYEY,
# 33 NULL -- ZDETECTEDFACE.ZRIGHTEYEX, (Not available on Ventura)
# 34 NULL -- ZDETECTEDFACE.ZRIGHTEYEY, (Not available on Ventura)
# 35 ZDETECTEDFACE.ZROLL,
# 36 ZDETECTEDFACE.ZSIZE,
# 37 ZDETECTEDFACE.ZYAW,
# 37 NULL -- ZDETECTEDFACE.ZYAW, (Not available on Ventura)
# 38 ZDETECTEDFACE.ZMASTERIDENTIFIER
for row in result:
@@ -310,8 +309,8 @@ def _process_faceinfo_5(photosdb):
face["righteyey"] = row[34]
face["roll"] = row[35]
face["size"] = row[36]
face["yaw"] = row[37]
face["pitch"] = 0.0 # not defined in Photos 5
face["yaw"] = 0 # Photos 4 only (this is in Photos 5-7, but dropped in Ventura so just don't support it)
face["pitch"] = 0 # not defined in Photos 5
photosdb._db_faceinfo_pk[pk] = face

View File

@@ -5,7 +5,7 @@
import logging
from .._constants import _DB_TABLE_NAMES, _PHOTOS_4_VERSION
from ..utils import _open_sql_file
from ..sqlite_utils import sqlite_open_ro
from .photosdb_utils import get_db_version
"""
@@ -48,7 +48,7 @@ def _process_scoreinfo_5(photosdb):
asset_table = _DB_TABLE_NAMES[photosdb._photos_ver]["ASSET"]
(conn, cursor) = _open_sql_file(db)
(conn, cursor) = sqlite_open_ro(db)
result = cursor.execute(
f"""

View File

@@ -3,14 +3,15 @@
ref: https://github.com/dogsheep/photos-to-sqlite/issues/16
"""
from functools import lru_cache
import logging
import pathlib
import uuid as uuidlib
from functools import lru_cache
from pprint import pformat
from .._constants import _PHOTOS_4_VERSION, SEARCH_CATEGORY_LABEL
from ..utils import _db_is_locked, _open_sql_file, normalize_unicode
from ..sqlite_utils import sqlite_open_ro, sqlite_db_is_locked
from ..utils import normalize_unicode
"""
This module should be imported in the class defintion of PhotosDB in photosdb.py
@@ -63,12 +64,12 @@ def _process_searchinfo(self):
logging.warning(f"could not find search db: {search_db_path}")
return None
if _db_is_locked(search_db_path):
if sqlite_db_is_locked(search_db_path):
search_db = self._copy_db_file(search_db_path)
else:
search_db = search_db_path
(conn, c) = _open_sql_file(search_db)
(conn, c) = sqlite_open_ro(search_db)
result = c.execute(
"""

View File

@@ -14,7 +14,7 @@ import tempfile
from collections import OrderedDict
from collections.abc import Iterable
from datetime import datetime, timedelta, timezone
from typing import List
from typing import List, Optional
from unicodedata import normalize
import bitmath
@@ -57,11 +57,10 @@ from ..photoinfo import PhotoInfo
from ..phototemplate import RenderOptions
from ..queryoptions import QueryOptions
from ..rich_utils import add_rich_markup_tag
from ..sqlite_utils import sqlite_open_ro, sqlite_db_is_locked
from ..utils import (
_check_file_exists,
_db_is_locked,
_get_os_version,
_open_sql_file,
get_last_library_path,
noop,
normalize_unicode,
@@ -309,7 +308,7 @@ class PhotosDB:
# Photos maintains an exclusive lock on the database file while Photos is open
# photoanalysisd sometimes maintains this lock even after Photos is closed
# In those cases, make a temp copy of the file for sqlite3 to read
if _db_is_locked(self._dbfile):
if sqlite_db_is_locked(self._dbfile):
verbose(f"Database locked, creating temporary copy.")
self._tmp_db = self._copy_db_file(self._dbfile)
@@ -325,7 +324,7 @@ class PhotosDB:
self._dbfile_actual = self._tmp_db = dbfile
verbose(f"Processing database {self._filepath(self._dbfile_actual)}")
# if database is exclusively locked, make a copy of it and use the copy
if _db_is_locked(self._dbfile_actual):
if sqlite_db_is_locked(self._dbfile_actual):
verbose(f"Database locked, creating temporary copy.")
self._tmp_db = self._copy_db_file(self._dbfile_actual)
@@ -578,7 +577,7 @@ class PhotosDB:
Returns:
tuple of (connection, cursor) to sqlite3 database
"""
return _open_sql_file(self._tmp_db)
return sqlite_open_ro(self._tmp_db)
def _copy_db_file(self, fname):
"""copies the sqlite database file to a temp file"""
@@ -642,7 +641,7 @@ class PhotosDB:
self._photos_ver = 4 # only used in Photos 5+
(conn, c) = _open_sql_file(self._tmp_db)
(conn, c) = sqlite_open_ro(self._tmp_db)
# get info to associate persons with photos
# then get detected faces in each photo and link to persons
@@ -1593,7 +1592,7 @@ class PhotosDB:
logging.debug(f"_process_database5")
verbose = self._verbose
verbose(f"Processing database.")
(conn, c) = _open_sql_file(self._tmp_db)
(conn, c) = sqlite_open_ro(self._tmp_db)
# some of the tables/columns have different names in different versions of Photos
photos_ver = get_db_model_version(self._tmp_db)
@@ -2858,16 +2857,16 @@ class PhotosDB:
def photos(
self,
keywords=None,
uuid=None,
persons=None,
albums=None,
images=True,
movies=True,
from_date=None,
to_date=None,
intrash=False,
):
keywords: Optional[List[str]] = None,
uuid: Optional[List[str]] = None,
persons: Optional[List[str]] = None,
albums: Optional[List[str]] = None,
images: bool = True,
movies: bool = True,
from_date: Optional[datetime] = None,
to_date: Optional[datetime] = None,
intrash: bool = False,
) -> List[PhotoInfo]:
"""Return a list of PhotoInfo objects
If called with no args, returns the entire database of photos
If called with args, returns photos matching the args (e.g. keywords, persons, etc.)
@@ -3287,6 +3286,8 @@ class PhotosDB:
if options.is_reference:
photos = [p for p in photos if p.isreference]
elif options.not_reference:
photos = [p for p in photos if not p.isreference]
if options.in_album:
photos = [p for p in photos if p.albums]

View File

@@ -12,9 +12,10 @@ from .._constants import (
_PHOTOS_5_VERSION,
_PHOTOS_6_MODEL_VERSION,
_PHOTOS_7_MODEL_VERSION,
_PHOTOS_8_MODEL_VERSION,
_TESTED_DB_VERSIONS,
)
from ..utils import _open_sql_file
from ..sqlite_utils import sqlite_open_ro
__all__ = [
"get_db_version",
@@ -36,7 +37,7 @@ def get_db_version(db_file):
version = None
(conn, c) = _open_sql_file(db_file)
(conn, c) = sqlite_open_ro(db_file)
# get database version
c.execute("SELECT value from LiGlobals where LiGlobals.keyPath is 'libraryVersion'")
@@ -63,7 +64,7 @@ def get_model_version(db_file):
version = None
(conn, c) = _open_sql_file(db_file)
(conn, c) = sqlite_open_ro(db_file)
# get database version
c.execute("SELECT MAX(Z_VERSION), Z_PLIST FROM Z_METADATA")
@@ -92,10 +93,12 @@ def get_db_model_version(db_file):
return 6
elif _PHOTOS_7_MODEL_VERSION[0] <= model_ver <= _PHOTOS_7_MODEL_VERSION[1]:
return 7
elif _PHOTOS_8_MODEL_VERSION[0] <= model_ver <= _PHOTOS_8_MODEL_VERSION[1]:
return 8
else:
logging.warning(f"Unknown model version: {model_ver}")
# cross our fingers and try latest version
return 7
return 8
class UnknownLibraryVersion(Exception):

View File

@@ -51,10 +51,15 @@ Valid filters are:
- `rsort`: Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
- `reverse`: Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].
- `uniq`: Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].
- `join(x)`: Join list of values with delimiter x, e.g. join(:): ['a', 'b', 'c'] => 'a:b:c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.
- `join(x)`: Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. e.g. join(): ['a', 'b', 'c'] => 'abc'.
- `append(x)`: Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].
- `prepend(x)`: Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].
- `remove(x)`: Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].
- `slice(start:stop:step)`: Slice list using same semantics as Python's list slicing, e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().
- `sslice(start:stop:step)`: [s(tring) slice] Slice values in a list using same semantics as Python's string slicing, e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().
- `filter(x)`: Filter list of values using predicate x; for example, `{folder_album|filter(contains Events)}` returns only folders/albums containing the word 'Events' in their path.
- `int`: Convert values in list to integer, e.g. 1.0 => 1. If value cannot be converted to integer, remove value from list. ['1.1', 'x'] => ['1']. See also float.
- `float`: Convert values in list to floating point number, e.g. 1 => 1.0. If value cannot be converted to float, remove value from list. ['1', 'x'] => ['1.0']. See also int.
e.g. if Photo keywords are `["FOO","bar"]`:

View File

@@ -20,7 +20,7 @@ from .datetime_formatter import DateTimeFormatter
from .exiftool import ExifToolCaching
from .path_utils import sanitize_dirname, sanitize_filename, sanitize_pathpart
from .text_detection import detect_text
from .utils import expand_and_validate_filepath, load_function
from .utils import expand_and_validate_filepath, load_function, uuid_to_shortuuid
__all__ = [
"RenderOptions",
@@ -141,6 +141,8 @@ TEMPLATE_SUBSTITUTIONS = {
"{exif.lens_model}": "Lens model from original photo's EXIF information as imported by Photos, e.g. 'iPhone 6s back camera 4.15mm f/2.2'",
"{moment}": "The moment title of the photo",
"{uuid}": "Photo's internal universally unique identifier (UUID) for the photo, a 36-character string unique to the photo, e.g. '128FB4C6-0B16-4E7D-9108-FB2E90DA1546'",
"{shortuuid}": "A shorter representation of photo's internal universally unique identifier (UUID) for the photo, "
+ "a 22-character string unique to the photo, e.g. 'JYsxugP9UjetmCbBCHXcmu'",
"{id}": "A unique number for the photo based on its primary key in the Photos database. "
+ "A sequential integer, e.g. 1, 2, 3...etc. Each asset associated with a photo (e.g. an image and Live Photo preview) will share the same id. "
+ "May be formatted using a python string format code. "
@@ -179,7 +181,8 @@ TEMPLATE_SUBSTITUTIONS = {
"{newline}": r"A newline: '\n'",
"{lf}": r"A line feed: '\n', alias for {newline}",
"{cr}": r"A carriage return: '\r'",
"{crlf}": r"a carriage return + line feed: '\r\n'",
"{crlf}": r"A carriage return + line feed: '\r\n'",
"{tab}": r":A tab: '\t'",
"{osxphotos_version}": f"The osxphotos version, e.g. '{__version__}'",
"{osxphotos_cmd_line}": "The full command line used to run osxphotos",
}
@@ -253,10 +256,24 @@ FILTER_VALUES = {
"rsort": "Sort list of values in reverse order, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].",
"reverse": "Reverse order of values, e.g. ['a', 'b', 'c'] => ['c', 'b', 'a'].",
"uniq": "Remove duplicate values, e.g. ['a', 'b', 'c', 'b', 'a'] => ['a', 'b', 'c'].",
"join(x)": "Join list of values with delimiter x, e.g. join(:): ['a', 'b', 'c'] => 'a:b:c'; the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters.",
"join(x)": "Join list of values with delimiter x, e.g. join(,): ['a', 'b', 'c'] => 'a,b,c'; "
+ "the DELIM option functions similar to join(x) but with DELIM, the join happens before being passed to any filters."
+ "May optionally be used without an argument, that is 'join()' which joins values together with no delimiter. "
+ "e.g. join(): ['a', 'b', 'c'] => 'abc'.",
"append(x)": "Append x to list of values, e.g. append(d): ['a', 'b', 'c'] => ['a', 'b', 'c', 'd'].",
"prepend(x)": "Prepend x to list of values, e.g. prepend(d): ['a', 'b', 'c'] => ['d', 'a', 'b', 'c'].",
"remove(x)": "Remove x from list of values, e.g. remove(b): ['a', 'b', 'c'] => ['a', 'c'].",
"slice(start:stop:step)": "Slice list using same semantics as Python's list slicing, "
+ "e.g. slice(1:3): ['a', 'b', 'c', 'd'] => ['b', 'c']; slice(1:4:2): ['a', 'b', 'c', 'd'] => ['b', 'd']; "
+ "slice(1:): ['a', 'b', 'c', 'd'] => ['b', 'c', 'd']; slice(:-1): ['a', 'b', 'c', 'd'] => ['a', 'b', 'c']; "
+ "slice(::-1): ['a', 'b', 'c', 'd'] => ['d', 'c', 'b', 'a']. See also sslice().",
"sslice(start:stop:step)": "[s(tring) slice] Slice values in a list using same semantics as Python's string slicing, "
+ "e.g. sslice(1:3):'abcd => 'bc'; sslice(1:4:2): 'abcd' => 'bd', etc. See also slice().",
"filter(x)": "Filter list of values using predicate x; for example, `{folder_album|filter(contains Events)}` returns only folders/albums containing the word 'Events' in their path.",
"int": "Convert values in list to integer, e.g. 1.0 => 1. If value cannot be converted to integer, remove value from list. "
+ "['1.1', 'x'] => ['1']. See also float.",
"float": "Convert values in list to floating point number, e.g. 1 => 1.0. If value cannot be converted to float, remove value from list. "
+ "['1', 'x'] => ['1.0']. See also int.",
}
# Just the substitutions without the braces
@@ -296,6 +313,7 @@ PUNCTUATION = {
"lf": "\n",
"cr": "\r",
"crlf": "\r\n",
"tab": "\t",
}
@@ -318,6 +336,7 @@ class RenderOptions:
dest_path: set to the destination path of the photo (for use by {function} template), only valid with --filename
filepath: set to value for filepath of the exported photo if you want to evaluate {filepath} template
quote: quote path templates for execution in the shell
caller: which command is calling the template (e.g. 'export')
"""
none_str: str = "_"
@@ -332,6 +351,7 @@ class RenderOptions:
dest_path: Optional[str] = None
filepath: Optional[str] = None
quote: bool = False
caller: str = "export"
class PhotoTemplateParser:
@@ -654,16 +674,18 @@ class PhotoTemplate:
vals = string_test(lambda v, c: v.endswith(c))
elif operator == "==":
match = sorted(vals) == sorted(conditional_value)
if (match and not negation) or (negation and not match):
vals = ["True"]
else:
vals = []
vals = (
["True"]
if (match and not negation) or (negation and not match)
else []
)
elif operator == "!=":
match = sorted(vals) != sorted(conditional_value)
if (match and not negation) or (negation and not match):
vals = ["True"]
else:
vals = []
vals = (
["True"]
if (match and not negation) or (negation and not match)
else []
)
elif operator == "<":
vals = comparison_test(lambda v, c: v < c)
elif operator == "<=":
@@ -777,7 +799,9 @@ class PhotoTemplate:
raise ValueError(
"SyntaxError: filename and function must not be null with {function::filename.py:function_name}"
)
vals = self.get_template_value_function(subfield, field_arg)
vals = self.get_template_value_function(
subfield, field_arg, self.options.caller
)
elif field in MULTI_VALUE_SUBSTITUTIONS or field.startswith("photo"):
vals = self.get_template_value_multi(
field, subfield, path_sep=field_arg, default=default
@@ -1078,6 +1102,8 @@ class PhotoTemplate:
value = self.photo.moment_info.title if self.photo.moment_info else None
elif field == "uuid":
value = self.photo.uuid
elif field == "shortuuid":
value = uuid_to_shortuuid(self.photo.uuid) if self.photo.uuid else None
elif field == "id":
value = format_str_value(self.photo._info["pk"], subfield)
elif field.startswith("album_seq") or field.startswith("folder_album_seq"):
@@ -1160,10 +1186,12 @@ class PhotoTemplate:
"split",
"chop",
"chomp",
"join",
"append",
"prepend",
"remove",
"slice",
"sslice",
"filter",
] and (args is None or not len(args)):
raise SyntaxError(f"{filter_} requires arguments")
@@ -1234,7 +1262,7 @@ class PhotoTemplate:
value = temp_values
elif filter_ == "join":
# join list of values with delimiter
delim = args
delim = args or ""
value = [delim.join(values)]
elif filter_ == "append":
# append value to list
@@ -1245,12 +1273,79 @@ class PhotoTemplate:
elif filter_ == "remove":
# remove value from list
value = [v for v in values if v != args]
elif filter_ == "slice":
# slice list of values
value = values[create_slice(args)]
elif filter_ == "sslice":
# slice each value in a list
slice_ = create_slice(args)
value = [v[slice_] for v in values]
elif filter_ == "filter":
# filter values based on a predicate
value = [v for v in values if self.filter_predicate(v, args)]
elif filter_ == "int":
# convert value to integer
value = values_to_int(values)
elif filter_ == "float":
# convert value to float
value = values_to_float(values)
elif filter_.startswith("function:"):
value = self.get_template_value_filter_function(filter_, args, values)
else:
value = []
return value
def filter_predicate(self, value: str, args: str) -> bool:
"""Return True if value passes predicate"""
# extract function name and arguments
if not args:
raise SyntaxError("Filter predicate requires arguments")
args = args.split(None, 1)
if args[0] == "not":
args = args[1:]
return not self.filter_predicate(value, " ".join(args))
predicate = args[0]
conditional_value = args[1].split("|")
def comparison_test(test_function):
"""Perform numerical comparisons using test_function"""
# returns True if any of the values match the condition
try:
return any(
bool(test_function(float(value), float(c)))
for c in conditional_value
)
except ValueError as e:
raise SyntaxError(
f"comparison operators may only be used with values that can be converted to numbers: {vals} {conditional_value}"
) from e
predicate_is_true = False
if predicate == "contains":
predicate_is_true = any(c in value for c in conditional_value)
elif predicate == "endswith":
predicate_is_true = any(value.endswith(c) for c in conditional_value)
elif predicate in ["matches", "=="]:
predicate_is_true = any(value == c for c in conditional_value)
elif predicate == "startswith":
predicate_is_true = any(value.startswith(c) for c in conditional_value)
elif predicate == "!=":
predicate_is_true = any(value != c for c in conditional_value)
elif predicate == "<":
predicate_is_true = comparison_test(lambda v, c: v < c)
elif predicate == "<=":
predicate_is_true = comparison_test(lambda v, c: v <= c)
elif predicate == ">":
predicate_is_true = comparison_test(lambda v, c: v > c)
elif predicate == ">=":
predicate_is_true = comparison_test(lambda v, c: v >= c)
else:
raise SyntaxError(f"Invalid predicate: {predicate}")
return predicate_is_true
def get_template_value_multi(self, field, subfield, path_sep, default):
"""lookup value for template field (multi-value template substitutions)
@@ -1438,10 +1533,17 @@ class PhotoTemplate:
def get_template_value_function(
self,
subfield,
field_arg,
subfield: str,
field_arg: Optional[str],
caller: str,
):
"""Get template value from external function"""
"""Get template value from external function
Args:
subfield: the filename and function name in for filename.py::function
field_arg: the argument to pass to the function
caller: the calling source of the template ('export' or 'import')
"""
if "::" not in subfield:
raise ValueError(
@@ -1460,8 +1562,17 @@ class PhotoTemplate:
# if no uuid, then template is being validated but not actually run
# so don't run the function
values = []
else:
elif caller == "export":
# function signature is:
# def example(photo: PhotoInfo, options: ExportOptions, args: Optional[str] = None, **kwargs) -> Union[List, str]:
values = template_func(self.photo, options=self.options, args=field_arg)
elif caller == "import":
# function signature is:
# def example(filepath: pathlib.Path, args: Optional[str] = None, **kwargs) -> Union[List, str]:
# the PhotoInfoFromFile class used by import sets `path` to the path of the file being imported
values = template_func(pathlib.Path(self.photo.path), args=field_arg)
else:
raise ValueError(f"Unhandled caller: {caller}")
if not isinstance(values, (str, list)):
raise TypeError(
@@ -1608,20 +1719,18 @@ def _get_pathlib_value(field, value, quote):
if len(parts) == 1:
return shlex.quote(value) if quote else value
if len(parts) > 2:
raise ValueError(f"Illegal value for path template: {field}")
path = parts[0]
attribute = parts[1]
path = pathlib.Path(value)
try:
val = getattr(path, attribute)
val_str = str(val)
if quote:
val_str = shlex.quote(val_str)
return val_str
except AttributeError:
raise ValueError("Illegal value for path template: {attribute}")
for attribute in parts[1:]:
try:
val = getattr(path, attribute)
path = pathlib.Path(val)
except AttributeError as e:
raise ValueError(f"Illegal value for filepath template: {attribute}") from e
val_str = str(val)
if quote:
val_str = shlex.quote(val_str)
return val_str
def format_str_value(value, format_str):
@@ -1669,3 +1778,43 @@ def _get_detected_text(photo, confidence=TEXT_DETECTION_CONFIDENCE_THRESHOLD):
# so the first time this gets called is slow but repeated accesses are fast
detected_text = photo._detected_text()
return [text for text, conf in detected_text if conf >= confidence]
def create_slice(args):
"""Create a slice object from a string of args in form "start:end:step" """
slice_args = args.split(":")
if len(slice_args) == 1:
start = int(slice_args[0] or 0)
end = None
step = None
elif len(slice_args) == 2:
start, end = slice_args
start = int(start) if start != "" else None
end = int(end) if end != "" else None
step = None
elif len(slice_args) == 3:
start, end, step = slice_args
start = int(start) if start != "" else None
end = int(end) if end != "" else None
step = int(step) if step != "" else None
else:
raise SyntaxError(f"Invalid slice: {args}")
return slice(start, end, step)
def values_to_int(values: List[str]) -> List[str]:
"""Convert a list of strings to str representation of ints, if possible, otherwise strip values from list"""
int_values = []
for v in values:
with suppress(ValueError):
int_values.append(str(int(float(v))))
return int_values
def values_to_float(values: List[str]) -> List[str]:
"""Convert a list of strings to str representation of float, if possible, otherwise strip values from list"""
float_values = []
for v in values:
with suppress(ValueError):
float_values.append(str(float(v)))
return float_values

View File

@@ -70,6 +70,7 @@ class QueryOptions:
not_missing: search for non-missing photos
not_panorama: search for non-panorama photos
not_portrait: search for non-portrait photos
not_reference: search for photos not stored by reference (that is, they are managed by Photos)
not_screenshot: search for non-screenshot photos
not_selfie: search for non-selfie photos
not_shared: search for non-shared photos
@@ -151,6 +152,7 @@ class QueryOptions:
not_missing: Optional[bool] = None
not_panorama: Optional[bool] = None
not_portrait: Optional[bool] = None
not_reference: Optional[bool] = None
not_screenshot: Optional[bool] = None
not_selfie: Optional[bool] = None
not_shared: Optional[bool] = None

57
osxphotos/sqlite_utils.py Normal file
View File

@@ -0,0 +1,57 @@
"""sqlite utils for use by osxphotos"""
import os.path
import pathlib
import sqlite3
from typing import List, Tuple
def sqlite_open_ro(dbname: str) -> Tuple[sqlite3.Connection, sqlite3.Cursor]:
"""opens sqlite file dbname in read-only mode
returns tuple of (connection, cursor)"""
try:
dbpath = pathlib.Path(dbname).resolve()
conn = sqlite3.connect(f"{dbpath.as_uri()}?mode=ro", timeout=1, uri=True)
c = conn.cursor()
except sqlite3.Error as e:
raise sqlite3.Error(
f"An error occurred opening sqlite file: {e} {dbname}"
) from e
return (conn, c)
def sqlite_db_is_locked(dbname):
"""check to see if a sqlite3 db is locked
returns True if database is locked, otherwise False
dbname: name of database to test"""
# first, check to see if lock file exists, if so, assume the file is locked
lock_name = f"{dbname}.lock"
if os.path.exists(lock_name):
return True
# no lock file so try to read from the database to see if it's locked
locked = None
try:
(conn, c) = sqlite_open_ro(dbname)
c.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;")
conn.close()
locked = False
except Exception:
locked = True
return locked
def sqlite_tables(conn: sqlite3.Connection) -> List[str]:
"""Returns list of tables found in sqlite db"""
results = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table';"
).fetchall()
return [row[0] for row in results]
def sqlite_columns(conn: sqlite3.Connection, table: str) -> List[str]:
"""Returns list of column names found in table in sqlite database"""
results = conn.execute(f"PRAGMA table_info({table});")
return [row[1] for row in results]

View File

@@ -92,6 +92,12 @@
% endif
</%def>
<%def name="xmp_rating(rating)">
% if rating is not None:
<xmp:Rating>${rating}</xmp:Rating>
% endif
</%def>
<%def name="gps_info(latitude, longitude)">
% if latitude is not None and longitude is not None:
<exif:GPSLongitude>${int(abs(longitude))},${(abs(longitude) % 1) * 60}${"E" if longitude >= 0 else "W"}</exif:GPSLongitude>
@@ -174,6 +180,7 @@
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
${adobe_createdate(photo.date)}
${adobe_modifydate(photo.date)}
${xmp_rating(rating)}
</rdf:Description>
<rdf:Description rdf:about=""

View File

@@ -92,6 +92,12 @@
% endif
</%def>
<%def name="xmp_rating(rating)">
% if rating is not None:
<xmp:Rating>${rating}</xmp:Rating>
% endif
</%def>
<%def name="gps_info(latitude, longitude)">
% if latitude is not None and longitude is not None:
<exif:GPSLongitude>${int(abs(longitude))},${(abs(longitude) % 1) * 60}${"E" if longitude >= 0 else "W"}</exif:GPSLongitude>
@@ -174,6 +180,7 @@
xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
${adobe_createdate(photo.date)}
${adobe_modifydate(photo.date)}
${xmp_rating(rating)}
</rdf:Description>
<rdf:Description rdf:about=""

View File

@@ -51,6 +51,22 @@ the exported files would be:
/path/to/export/Travel/IMG_1234.JPG
/path/to/export/Vacation/IMG_1234.JPG
If your photos are organized in folders and albums in Photos you can preserve this structure on export by using the `{folder_album}` template field with the `--directory` option. For example, if you have a photo in the album `Vacation` which is in the `Travel` folder, the following command would export the photo to the `Travel/Vacation` directory:
`osxphotos export /path/to/export --directory "{folder_album}"`
Photos can belong to more than one album. In this case, the template field `{folder_album}` will expand to all the album names that the photo belongs to. For example, if a photo belongs to the albums `Vacation` and `Travel`, the template field `{folder_album}` would expand to `Vacation`, `Travel`. If the photo belongs to no albums, the template field `{folder_album}` would expand to "_" (the default value).
All template fields including `{folder_album}` can be further filtered using a number of different filters. To convert all directory names to lower case for example, use the `lower` filter:
`osxphotos export /path/to/export --directory "{folder_album|lower}"`
If all your photos were organized into various albums under a folder named `Events` but some where also included in other top-level albums and you wanted to export only the `Events` folder, you could use the `filter` option to filter out the other top-level albums by selecting only those folder/album paths that start with `Events`:
`osxphotos export /path/to/export --directory "{folder_album|filter(startswith Events)}"`
You can learn more about the other filters using `osxphotos help export`.
## Specify exported filename
By default, osxphotos will use the original filename of the photo when exporting. That is, the filename the photo had when it was taken or imported into Photos. This is often something like `IMG_1234.JPG` or `DSC05678.dng`. osxphotos allows you to specify a custom filename template using the `--filename` option in the same way as `--directory` allows you to specify a custom directory name. For example, Photos allows you specify a title or caption for a photo and you can use this in place of the original filename:
@@ -230,6 +246,14 @@ You can use the `--report` option to create a report, in comma-separated values
`osxphotos export /path/to/export --report export.csv`
You can also create reports in JSON or SQLite format by changing the extension of the report filename. For example, to create a JSON report:
`osxphotos export /path/to/export --report export.json`
And to create a SQLite report:
`osxphotos export /path/to/export --report export.sqlite`
## Exporting only certain photos
By default, osxphotos will export your entire Photos library. If you want to export only certain photos, osxphotos provides a rich set of "query options" that allow you to query the Photos database to filter out only certain photos that match your query criteria. The tutorial does not cover all the query options as there are over 50 of them--read the help text (`osxphotos help export`) to better understand the available query options. No matter which subset of photos you would like to export, there is almost certainly a way for osxphotos to filter these. For example, you can filter for only images that contain certain keywords or images without a title, images from a specific time of day or specific date range, images contained in specific albums, etc.

View File

@@ -2,7 +2,6 @@
import datetime
import fnmatch
import glob
import hashlib
import importlib
import inspect
@@ -12,16 +11,17 @@ import os.path
import pathlib
import platform
import re
import sqlite3
import subprocess
import sys
import unicodedata
import urllib.parse
from plistlib import load as plistload
from typing import Callable, List, Optional, Tuple, Union
from uuid import UUID
import CoreFoundation
import requests
import shortuuid
from ._constants import UNICODE_FORMAT
@@ -41,6 +41,8 @@ __all__ = [
"normalize_fs_path",
"normalize_unicode",
"pluralize",
"shortuuid_to_uuid",
"uuid_to_shortuuid",
]
@@ -359,44 +361,6 @@ def list_directory(
return files
def _open_sql_file(dbname):
"""opens sqlite file dbname in read-only mode
returns tuple of (connection, cursor)"""
try:
dbpath = pathlib.Path(dbname).resolve()
conn = sqlite3.connect(f"{dbpath.as_uri()}?mode=ro", timeout=1, uri=True)
c = conn.cursor()
except sqlite3.Error as e:
sys.exit(f"An error occurred opening sqlite file: {e.args[0]} {dbname}")
return (conn, c)
def _db_is_locked(dbname):
"""check to see if a sqlite3 db is locked
returns True if database is locked, otherwise False
dbname: name of database to test"""
# first, check to see if lock file exists, if so, assume the file is locked
lock_name = f"{dbname}.lock"
if os.path.exists(lock_name):
logging.debug(f"{dbname} is locked")
return True
# no lock file so try to read from the database to see if it's locked
locked = None
try:
(conn, c) = _open_sql_file(dbname)
c.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;")
conn.close()
logging.debug(f"{dbname} is not locked")
locked = False
except:
logging.debug(f"{dbname} is locked")
locked = True
return locked
def normalize_unicode(value):
"""normalize unicode data"""
if value is None:
@@ -534,3 +498,13 @@ def hexdigest(strval: str) -> str:
h = hashlib.blake2b(digest_size=20)
h.update(bytes(strval, "utf-8"))
return h.hexdigest()
def uuid_to_shortuuid(uuid: str) -> str:
"""Convert uuid to shortuuid"""
return str(shortuuid.encode(UUID(uuid)))
def shortuuid_to_uuid(short_uuid: str) -> str:
"""Convert shortuuid to uuid"""
return str(shortuuid.decode(short_uuid)).upper()

View File

@@ -24,6 +24,7 @@ PyYAML>=5.4.1,<6.0.0
requests>=2.27.1,<3.0.0
rich_theme_manager>=0.11.0
rich>=11.2.0,<13.0.0
shortuuid==1.0.9
tenacity>=8.0.1,<9.0.0
textx>=2.3.0,<2.4.0
toml>=0.10.2,<0.11.0

View File

@@ -99,6 +99,7 @@ setup(
"requests>=2.27.1,<3.0.0",
"rich>=11.2.0,<13.0.0",
"rich_theme_manager>=0.11.0",
"shortuuid==1.0.9",
"tenacity>=8.0.1,<9.0.0",
"textx>=2.3.0,<3.0.0",
"toml>=0.10.2,<0.11.0",

View File

@@ -21,6 +21,12 @@ Some of the export tests rely on photos in my local library and will look for `O
One test for locale does not run on GitHub's automated workflow and will look for `OSXPHOTOS_TEST_LOCALE=1` to determine if it should be run. If you want to run this test, set the environment variable.
A couple of tests require interaction with Photos and configuring a specific test library. Currently these run only on Catalina. The tests must be specified by using a pytest flag. Only one of these interactive tests can be run at a time. The current flags are:
--addalbum: test --add-to-album options
--timewarp: test `osxphotos timewarp`
--test-import: test `osxphotos import`
## Test Photo Libraries
**Important**: The test code uses several test photo libraries created on various version of MacOS. If you need to inspect one of these or modify one for a test, make a copy of the library (for example, copy it to your ~/Pictures folder) then open the copy in Photos. Once done, copy the revised library back to the tests/ folder. If you do not do this, the Photos background process photoanalysisd will forever try to process the library resulting in updates to the database which will cause git to see changes to the file you didn't intend. I'm not aware of any way to disassociate photoanalysisd from the library once you've opened it in Photos.

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

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>tc1.local</string>
<key>hostuuid</key>
<string>A22F6630-733E-54D7-99F1-B3805B38E11B</string>
<key>pid</key>
<integer>548</integer>
<key>processname</key>
<string>photolibraryd</string>
<key>uid</key>
<integer>501</integer>
</dict>
</plist>

View File

@@ -0,0 +1,28 @@
<?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/>
<key>renamePerson</key>
<array/>
</dict>
</plist>

View File

@@ -0,0 +1,18 @@
<?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>featureFlags</key>
<string>63</string>
<key>featuredContentAllowed</key>
<string>1</string>
<key>localeIdentifier</key>
<string>en_GB</string>
<key>sceneTaxonomySHA</key>
<string>35f76559cb0770342bcea46fce0e3f562844ab7506e0e4b4ad1256abb0a07be2,4afa5d3c45c08a664cf73cff957aaeeae3a325d2970aada51268407b9ad0f03e</string>
<key>searchIndexVersion</key>
<string>16016</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

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