Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ca681ee4f | ||
|
|
66f6002a57 | ||
|
|
d845e9b66e | ||
|
|
991511af07 | ||
|
|
3c98906158 | ||
|
|
08b806ff7d | ||
|
|
b5f4c48ec9 | ||
|
|
b7cfd1da9b | ||
|
|
c88fc75013 | ||
|
|
46738d05b2 | ||
|
|
1e053aa708 | ||
|
|
c7e3a552db | ||
|
|
5979d6245d | ||
|
|
099afdb3ad | ||
|
|
8e3578e29b | ||
|
|
38a5998063 | ||
|
|
2103d8bcad | ||
|
|
26a9028497 | ||
|
|
e41f89480a | ||
|
|
db3c37fb5b | ||
|
|
3d83e184b8 | ||
|
|
28e03ee86a | ||
|
|
fac45c7141 | ||
|
|
f4154e8691 | ||
|
|
b4c4d9fb90 | ||
|
|
402bbbdbae | ||
|
|
f7771909d6 | ||
|
|
76625b9e84 | ||
|
|
fb4329e0ed | ||
|
|
2b52460de7 | ||
|
|
f8f9bd7b93 | ||
|
|
5d33dcdcc3 | ||
|
|
b4caea15fa | ||
|
|
6e33540e60 | ||
|
|
2e85f9be89 | ||
|
|
7484c7b994 | ||
|
|
f279217118 | ||
|
|
b998684821 | ||
|
|
f3557d1991 | ||
|
|
855d417e81 | ||
|
|
bd33b61882 | ||
|
|
337d422346 | ||
|
|
7497a02aaf | ||
|
|
1fadea8864 | ||
|
|
5a43fb7410 | ||
|
|
30bf06e794 | ||
|
|
091adc9925 | ||
|
|
f000f21ba5 |
@@ -326,6 +326,43 @@
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -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:
|
||||
|
||||
@@ -1815,6 +1815,9 @@ Valid filters are:
|
||||
- `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"]`:
|
||||
|
||||
@@ -2005,7 +2008,7 @@ cog.out(get_template_field_table())
|
||||
|{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.5'|
|
||||
|{osxphotos_version}|The osxphotos version, e.g. '0.51.2'|
|
||||
|{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|
|
||||
|
||||
87
CHANGELOG.md
87
CHANGELOG.md
@@ -4,6 +4,93 @@ 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.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
|
||||
|
||||
295
README.md
295
README.md
@@ -5,8 +5,9 @@
|
||||
[](https://github.com/RhetTbull/osxphotos/workflows/Tests/badge.svg)
|
||||

|
||||
[](https://pepy.tech/project/osxphotos)
|
||||
[](https://www.reddit.com/r/osxphotos/)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors)
|
||||
[](#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.
|
||||
@@ -141,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...
|
||||
@@ -774,6 +777,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.
|
||||
@@ -1051,6 +1057,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
|
||||
@@ -1191,6 +1201,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
|
||||
@@ -1494,6 +1527,15 @@ Valid filters are:
|
||||
• 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"]:
|
||||
|
||||
@@ -1922,7 +1964,7 @@ Substitution Description
|
||||
{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.5'
|
||||
{osxphotos_version} The osxphotos version, e.g. '0.51.2'
|
||||
{osxphotos_cmd_line} The full command line used to run osxphotos
|
||||
|
||||
The following substitutions may result in multiple values. Thus if specified
|
||||
@@ -2212,6 +2254,9 @@ Valid filters are:
|
||||
- `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"]`:
|
||||
|
||||
@@ -2399,7 +2444,7 @@ The following template field substitutions are availabe for use the templating s
|
||||
|{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.5'|
|
||||
|{osxphotos_version}|The osxphotos version, e.g. '0.51.2'|
|
||||
|{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|
|
||||
@@ -2424,244 +2469,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.
|
||||
@@ -2730,6 +2537,12 @@ 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>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ furo
|
||||
m2r2
|
||||
pdbpp
|
||||
pyinstaller==4.10
|
||||
pytest-cov==3.0.0
|
||||
pytest-mock
|
||||
pytest==7.0.1
|
||||
Sphinx
|
||||
|
||||
@@ -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: 426da14f51484f2c31c9935686a2dd0e
|
||||
config: 18c2b25ef56807d10442ba0ee766ff2f
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -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.5 documentation</title>
|
||||
<title>Overview: module code - osxphotos 0.51.2 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.5 documentation</div></a>
|
||||
<a href="../index.html"><div class="brand">osxphotos 0.51.2 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.5 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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>
|
||||
|
||||
@@ -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.50.4 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.50.4 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.50.4 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">
|
||||
@@ -314,6 +314,7 @@
|
||||
<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>
|
||||
|
||||
@@ -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.50.5 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.50.5 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.5 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">
|
||||
|
||||
@@ -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.5 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.5 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.5 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>
|
||||
|
||||
373
docs/_modules/osxphotos/photosalbum.html
Normal file
373
docs/_modules/osxphotos/photosalbum.html
Normal 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">-></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">-></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">></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 © 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>
|
||||
@@ -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 — 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">""" PhotosDB method for processing comments and likes on shared photos.</span>
|
||||
<span class="sd"> Do not import this module directly """</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">"""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"> """</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"><=</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">"""Class for shared photo comments"""</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">"""Class for shared photo likes"""</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">"""process comments and likes info for Photos <= 4</span>
|
||||
<span class="sd"> photosdb: PhotosDB instance"""</span>
|
||||
<span class="sd">"""process comments and likes info for Photos <= 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">"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="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">"""process comments and likes info for Photos >= 5</span>
|
||||
<span class="sd"> photosdb: PhotosDB instance"""</span>
|
||||
<span class="sd">"""process comments and likes info for Photos >= 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">"ASSET"</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">"""</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 != ""</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 != ""</span>
|
||||
<span class="sd"> AND HASHEDPERSONID != ""</span>
|
||||
<span class="sd"> AND NOT (FIRSTNAME IS NULL AND LASTNAME IS NULL)</span>
|
||||
<span class="sd"> """</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">"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="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">"""</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 "like"</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'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"> """</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 "like"</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'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">"full_name"</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">"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">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">"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="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">"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="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">"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="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 © 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">
|
||||
©2021, Rhet Turnbull.
|
||||
<aside class="toc-drawer no-toc">
|
||||
|
||||
|
|
||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 4.4.0</a>
|
||||
& <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>
|
||||
@@ -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.4 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.4 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.4 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">
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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.3 documentation</title>
|
||||
<title>osxphotos.phototemplate - osxphotos 0.51.2 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.3 documentation</div></a>
|
||||
<a href="../../index.html"><div class="brand">osxphotos 0.51.2 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.3 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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">
|
||||
@@ -464,6 +464,11 @@
|
||||
<span class="o">+</span> <span class="s2">"slice(::-1): ['a', 'b', 'c', 'd'] => ['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 => 'bc'; sslice(1:4:2): 'abcd' => '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 => 1. If value cannot be converted to integer, remove value from list. "</span>
|
||||
<span class="o">+</span> <span class="s2">"['1.1', 'x'] => ['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 => 1.0. If value cannot be converted to float, remove value from list. "</span>
|
||||
<span class="o">+</span> <span class="s2">"['1', 'x'] => ['1.0']. See also int."</span><span class="p">,</span>
|
||||
<span class="p">}</span>
|
||||
|
||||
<span class="c1"># Just the substitutions without the braces</span>
|
||||
@@ -525,6 +530,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>
|
||||
@@ -539,6 +545,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>
|
||||
@@ -861,16 +868,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">"<"</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"><</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>
|
||||
@@ -984,7 +993,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>
|
||||
@@ -1374,6 +1385,7 @@
|
||||
<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>
|
||||
|
||||
@@ -1462,12 +1474,72 @@
|
||||
<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">-></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">"<"</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"><</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">"<="</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"><=</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">">"</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">></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">">="</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">>=</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>
|
||||
|
||||
@@ -1655,10 +1727,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>
|
||||
@@ -1677,8 +1756,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) -> 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) -> 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>
|
||||
@@ -1825,20 +1913,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">></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>
|
||||
@@ -1908,6 +1994,24 @@
|
||||
<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">-></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">-></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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -61,6 +61,9 @@ Valid filters are:
|
||||
* `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"]``\ :
|
||||
|
||||
@@ -346,7 +349,7 @@ Template Substitutions
|
||||
* - {crlf}
|
||||
- a carriage return + line feed: '\r\n'
|
||||
* - {osxphotos_version}
|
||||
- The osxphotos version, e.g. '0.50.5'
|
||||
- The osxphotos version, e.g. '0.51.2'
|
||||
* - {osxphotos_cmd_line}
|
||||
- The full command line used to run osxphotos
|
||||
* - {album}
|
||||
|
||||
2
docs/_static/documentation_options.js
vendored
2
docs/_static/documentation_options.js
vendored
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.50.5',
|
||||
VERSION: '0.51.2',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
200
docs/cli.html
200
docs/cli.html
@@ -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.5 documentation</title>
|
||||
<title>OSXPhotos Command Line Interface (CLI) - osxphotos 0.51.2 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.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.51.2 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.5 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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">
|
||||
@@ -817,6 +817,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 +1054,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 +1164,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 you’re 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"><KEEP_PATH></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 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/<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"><ALBUM></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 doesn’t 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 don’t know how well it will work on large export sets.</p>
|
||||
@@ -1357,6 +1372,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"><ALBUM_TEMPLATE></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"><TITLE_TEMPLATE></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"><DESCRIPTION_TEMPLATE></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"><KEYWORD_TEMPLATE></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"><LATITUDE</span> <span class="pre">LONGITUDE></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 file’s 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"><EXIFTOOL_PATH></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"><RELATIVE_TO_PATH></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"><split_folder></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"><GLOB></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"><REPORT_FILE></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 today’s 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>Don’t 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"><THEME></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>
|
||||
@@ -1498,6 +1642,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"><EXPORT_PATH></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"><PHOTOS_LIBRARY_PATH></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"><THEME></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>
|
||||
@@ -1871,6 +2053,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>
|
||||
@@ -2295,6 +2482,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>
|
||||
@@ -2761,12 +2953,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>
|
||||
|
||||
@@ -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.5 documentation</title>
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.51.2 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.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.51.2 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.5 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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
|
||||
|
||||
@@ -1188,6 +1267,8 @@
|
||||
<li><a href="cli.html#cmdoption-osxphotos-repl-not-cloudasset">osxphotos-repl command line option</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li>
|
||||
--not-favorite
|
||||
|
||||
@@ -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>
|
||||
@@ -1544,6 +1636,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 +1663,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 +1808,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>
|
||||
@@ -1744,8 +1852,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>
|
||||
@@ -1782,6 +1894,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>
|
||||
@@ -1798,6 +1914,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>
|
||||
@@ -1949,6 +2067,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>
|
||||
@@ -1960,6 +2082,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>
|
||||
@@ -1991,7 +2120,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>
|
||||
@@ -2005,6 +2143,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 +2152,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>
|
||||
@@ -2019,6 +2161,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>
|
||||
@@ -2033,9 +2177,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>
|
||||
@@ -2056,19 +2209,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>
|
||||
@@ -2090,6 +2261,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>
|
||||
@@ -2098,6 +2271,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>
|
||||
@@ -2111,6 +2286,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>
|
||||
@@ -2120,6 +2297,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>
|
||||
@@ -2143,6 +2322,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>
|
||||
@@ -2152,6 +2335,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>
|
||||
@@ -2481,17 +2671,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>
|
||||
@@ -2832,10 +3033,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>
|
||||
@@ -2849,6 +3050,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>
|
||||
@@ -3073,6 +3276,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>
|
||||
@@ -3111,6 +3316,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>
|
||||
@@ -3165,6 +3372,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>
|
||||
@@ -3349,6 +3558,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>
|
||||
@@ -3362,6 +3656,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
|
||||
|
||||
@@ -3412,13 +3708,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>
|
||||
@@ -3556,6 +3867,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>
|
||||
@@ -3727,6 +4040,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>
|
||||
@@ -4055,6 +4370,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>
|
||||
|
||||
@@ -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.5 documentation</title>
|
||||
<title>osxphotos 0.51.2 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.5 documentation</div></a>
|
||||
<a href="#"><div class="brand">osxphotos 0.51.2 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.5 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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>
|
||||
|
||||
BIN
docs/objects.inv
BIN
docs/objects.inv
Binary file not shown.
@@ -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 OSXPhotos’s documentation!" href="index.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
|
||||
<title>OSXPhotos - osxphotos 0.50.5 documentation</title>
|
||||
<title>OSXPhotos - osxphotos 0.51.2 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.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.51.2 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.5 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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">
|
||||
|
||||
@@ -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.5 documentation</title>
|
||||
<title>OSXPhotos Python Package Overview - osxphotos 0.51.2 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.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.51.2 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.5 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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">
|
||||
|
||||
@@ -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.5 documentation</title>
|
||||
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Python Module Index - osxphotos 0.51.2 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.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.51.2 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.5 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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
@@ -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.5 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.2 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.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.51.2 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.5 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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
@@ -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.5 documentation</title>
|
||||
<title>OSXPhotos Template System - osxphotos 0.51.2 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.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.51.2 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.5 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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">
|
||||
@@ -241,6 +241,9 @@
|
||||
<li><p><cite>remove(x)</cite>: Remove x from list of values, e.g. remove(b): [‘a’, ‘b’, ‘c’] => [‘a’, ‘c’].</p></li>
|
||||
<li><p><cite>slice(start:stop:step)</cite>: 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().</p></li>
|
||||
<li><p><cite>sslice(start:stop:step)</cite>: [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().</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 => 1. If value cannot be converted to integer, remove value from list. [‘1.1’, ‘x’] => [‘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 => 1.0. If value cannot be converted to float, remove value from list. [‘1’, ‘x’] => [‘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">
|
||||
@@ -589,7 +592,7 @@
|
||||
<td><p>a carriage return + line feed: ‘rn’</p></td>
|
||||
</tr>
|
||||
<tr class="row-even"><td><p>{osxphotos_version}</p></td>
|
||||
<td><p>The osxphotos version, e.g. ‘0.50.5’</p></td>
|
||||
<td><p>The osxphotos version, e.g. ‘0.51.2’</p></td>
|
||||
</tr>
|
||||
<tr class="row-odd"><td><p>{osxphotos_cmd_line}</p></td>
|
||||
<td><p>The full command line used to run osxphotos</p></td>
|
||||
|
||||
@@ -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.5 documentation</title>
|
||||
<title>OSXPhotos Tutorial - osxphotos 0.51.2 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.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">osxphotos 0.51.2 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.5 documentation</span>
|
||||
<span class="sidebar-brand-text">osxphotos 0.51.2 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">
|
||||
|
||||
@@ -61,6 +61,9 @@ Valid filters are:
|
||||
* `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"]``\ :
|
||||
|
||||
@@ -346,7 +349,7 @@ Template Substitutions
|
||||
* - {crlf}
|
||||
- a carriage return + line feed: '\r\n'
|
||||
* - {osxphotos_version}
|
||||
- The osxphotos version, e.g. '0.50.5'
|
||||
- The osxphotos version, e.g. '0.51.2'
|
||||
* - {osxphotos_cmd_line}
|
||||
- The full command line used to run osxphotos
|
||||
* - {album}
|
||||
|
||||
43
examples/bad_photos.py
Normal file
43
examples/bad_photos.py
Normal 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,
|
||||
]
|
||||
)
|
||||
196
examples/strip_live.py
Normal file
196
examples/strip_live.py
Normal 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()
|
||||
27
examples/template_function_import.py
Normal file
27
examples/template_function_import.py
Normal 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
93
examples/xmp_rating.py
Normal 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,
|
||||
)
|
||||
@@ -12,6 +12,7 @@ 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",
|
||||
|
||||
@@ -118,6 +118,7 @@ _TESTED_OS_VERSIONS = [
|
||||
("12", "2"),
|
||||
("12", "3"),
|
||||
("12", "4"),
|
||||
("12", "5"),
|
||||
]
|
||||
|
||||
# Photos 5 has persons who are empty string if unidentified face
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.50.5"
|
||||
__version__ = "0.51.2"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -420,6 +420,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,
|
||||
|
||||
@@ -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",
|
||||
@@ -730,6 +757,7 @@ def export(
|
||||
exportdb,
|
||||
external_edit,
|
||||
favorite,
|
||||
favorite_rating,
|
||||
filename_template,
|
||||
finder_tag_keywords,
|
||||
finder_tag_template,
|
||||
@@ -749,6 +777,7 @@ def export(
|
||||
is_reference,
|
||||
jpeg_ext,
|
||||
jpeg_quality,
|
||||
keep,
|
||||
keyword_template,
|
||||
keyword,
|
||||
label,
|
||||
@@ -776,6 +805,7 @@ def export(
|
||||
not_live,
|
||||
not_panorama,
|
||||
not_portrait,
|
||||
not_reference,
|
||||
not_screenshot,
|
||||
not_selfie,
|
||||
not_shared,
|
||||
@@ -949,6 +979,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 +996,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 +1026,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
|
||||
@@ -1098,13 +1132,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 +1395,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 +1508,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,
|
||||
@@ -1678,9 +1717,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 +1773,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 +1827,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 +1993,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 +2108,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 +2194,7 @@ def export_photo_to_directory(
|
||||
export_original,
|
||||
export_preview,
|
||||
export_raw,
|
||||
favorite_rating,
|
||||
filename,
|
||||
fileutil,
|
||||
force_update,
|
||||
@@ -2203,6 +2256,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 +2490,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 +2506,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 +2562,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
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
156
osxphotos/cli/orphans.py
Normal 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]
|
||||
@@ -85,14 +85,32 @@ def inspect_photo(
|
||||
+ 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: ')}",
|
||||
@@ -106,7 +124,7 @@ def inspect_photo(
|
||||
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))
|
||||
@@ -231,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: ")
|
||||
|
||||
@@ -127,6 +127,7 @@ def query(
|
||||
not_missing,
|
||||
not_panorama,
|
||||
not_portrait,
|
||||
not_reference,
|
||||
not_screenshot,
|
||||
not_selfie,
|
||||
not_shared,
|
||||
@@ -176,7 +177,6 @@ def query(
|
||||
from_date,
|
||||
from_time,
|
||||
has_raw,
|
||||
is_reference,
|
||||
keyword,
|
||||
label,
|
||||
max_size,
|
||||
@@ -220,6 +220,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(
|
||||
@@ -309,6 +310,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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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(
|
||||
"""
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
@@ -40,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(
|
||||
"""
|
||||
@@ -179,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"""
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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(
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
|
||||
@@ -15,7 +15,7 @@ from .._constants import (
|
||||
_PHOTOS_8_MODEL_VERSION,
|
||||
_TESTED_DB_VERSIONS,
|
||||
)
|
||||
from ..utils import _open_sql_file
|
||||
from ..sqlite_utils import sqlite_open_ro
|
||||
|
||||
__all__ = [
|
||||
"get_db_version",
|
||||
@@ -37,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'")
|
||||
@@ -64,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")
|
||||
|
||||
@@ -57,6 +57,9 @@ Valid filters are:
|
||||
- `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"]`:
|
||||
|
||||
|
||||
@@ -268,6 +268,11 @@ FILTER_VALUES = {
|
||||
+ "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
|
||||
@@ -329,6 +334,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 = "_"
|
||||
@@ -343,6 +349,7 @@ class RenderOptions:
|
||||
dest_path: Optional[str] = None
|
||||
filepath: Optional[str] = None
|
||||
quote: bool = False
|
||||
caller: str = "export"
|
||||
|
||||
|
||||
class PhotoTemplateParser:
|
||||
@@ -665,16 +672,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 == "<=":
|
||||
@@ -788,7 +797,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
|
||||
@@ -1178,6 +1189,7 @@ class PhotoTemplate:
|
||||
"remove",
|
||||
"slice",
|
||||
"sslice",
|
||||
"filter",
|
||||
] and (args is None or not len(args)):
|
||||
raise SyntaxError(f"{filter_} requires arguments")
|
||||
|
||||
@@ -1266,12 +1278,72 @@ class PhotoTemplate:
|
||||
# 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)
|
||||
|
||||
@@ -1459,10 +1531,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(
|
||||
@@ -1481,8 +1560,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(
|
||||
@@ -1629,20 +1717,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):
|
||||
@@ -1712,3 +1798,21 @@ def create_slice(args):
|
||||
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
|
||||
|
||||
@@ -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
57
osxphotos/sqlite_utils.py
Normal 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]
|
||||
@@ -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=""
|
||||
|
||||
@@ -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=""
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import datetime
|
||||
import fnmatch
|
||||
import glob
|
||||
import hashlib
|
||||
import importlib
|
||||
import inspect
|
||||
@@ -12,7 +11,6 @@ import os.path
|
||||
import pathlib
|
||||
import platform
|
||||
import re
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import unicodedata
|
||||
@@ -363,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:
|
||||
|
||||
@@ -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 |
@@ -15,6 +15,9 @@ from .test_catalina_10_15_7 import UUID_DICT_LOCAL
|
||||
# run timewarp tests (configured with --timewarp)
|
||||
TEST_TIMEWARP = False
|
||||
|
||||
# run import tests (configured with --import)
|
||||
TEST_IMPORT = False
|
||||
|
||||
# don't clean up crash logs (configured with --no-cleanup)
|
||||
NO_CLEANUP = False
|
||||
|
||||
@@ -42,6 +45,7 @@ def get_os_version():
|
||||
OS_VER = get_os_version()[1]
|
||||
if OS_VER == "15":
|
||||
TEST_LIBRARY = "tests/Test-10.15.7.photoslibrary"
|
||||
TEST_LIBRARY_IMPORT = TEST_LIBRARY
|
||||
from tests.config_timewarp_catalina import TEST_LIBRARY_TIMEWARP
|
||||
else:
|
||||
TEST_LIBRARY = None
|
||||
@@ -56,6 +60,13 @@ def setup_photos_timewarp():
|
||||
copy_photos_library(TEST_LIBRARY_TIMEWARP, delay=10)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def setup_photos_import():
|
||||
if not TEST_IMPORT:
|
||||
return
|
||||
copy_photos_library(TEST_LIBRARY_IMPORT, delay=10)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def reset_singletons():
|
||||
"""Need to clean up any ExifTool singletons between tests"""
|
||||
@@ -72,6 +83,12 @@ def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--timewarp", action="store_true", default=False, help="run --timewarp tests"
|
||||
)
|
||||
parser.addoption(
|
||||
"--test-import",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="run `osxphotos import` tests",
|
||||
)
|
||||
parser.addoption(
|
||||
"--no-cleanup",
|
||||
action="store_true",
|
||||
@@ -81,8 +98,18 @@ def pytest_addoption(parser):
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
if config.getoption("--addalbum") and config.getoption("--timewarp"):
|
||||
pytest.exit("--addalbum and --timewarp are mutually exclusive")
|
||||
if (
|
||||
sum(
|
||||
bool(x)
|
||||
for x in [
|
||||
config.getoption("--addalbum"),
|
||||
config.getoption("--timewarp"),
|
||||
config.getoption("--test-import"),
|
||||
]
|
||||
)
|
||||
> 1
|
||||
):
|
||||
pytest.exit("--addalbum, --timewarp, --test-import are mutually exclusive")
|
||||
|
||||
config.addinivalue_line(
|
||||
"markers", "addalbum: mark test as requiring --addalbum to run"
|
||||
@@ -90,12 +117,19 @@ def pytest_configure(config):
|
||||
config.addinivalue_line(
|
||||
"markers", "timewarp: mark test as requiring --timewarp to run"
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers", "test_import: mark test as requiring --test-import to run"
|
||||
)
|
||||
|
||||
# this is hacky but I can't figure out how to check config options in other fixtures
|
||||
if config.getoption("--timewarp"):
|
||||
global TEST_TIMEWARP
|
||||
TEST_TIMEWARP = True
|
||||
|
||||
if config.getoption("--test-import"):
|
||||
global TEST_IMPORT
|
||||
TEST_IMPORT = True
|
||||
|
||||
if config.getoption("--no-cleanup"):
|
||||
global NO_CLEANUP
|
||||
NO_CLEANUP = True
|
||||
@@ -118,6 +152,14 @@ def pytest_collection_modifyitems(config, items):
|
||||
if "timewarp" in item.keywords:
|
||||
item.add_marker(skip_timewarp)
|
||||
|
||||
if not (config.getoption("--test-import") and TEST_LIBRARY_IMPORT is not None):
|
||||
skip_test_import = pytest.mark.skip(
|
||||
reason="need --test-import option and MacOS Catalina to run"
|
||||
)
|
||||
for item in items:
|
||||
if "test_import" in item.keywords:
|
||||
item.add_marker(skip_test_import)
|
||||
|
||||
|
||||
def copy_photos_library(photos_library, delay=0):
|
||||
"""copy the test library and open Photos, returns path to copied library"""
|
||||
|
||||
BIN
tests/test-images/IMG_4179.jpeg
Normal file
BIN
tests/test-images/IMG_4179.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
BIN
tests/test-images/Jellyfish.mov
Normal file
BIN
tests/test-images/Jellyfish.mov
Normal file
Binary file not shown.
@@ -9,6 +9,7 @@ import os.path
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import time
|
||||
@@ -987,6 +988,11 @@ CLI_EXPORT_LIVE_EDITED = [
|
||||
"IMG_4813_edited.mov",
|
||||
]
|
||||
|
||||
UUID_FAVORITE = "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51"
|
||||
FILE_FAVORITE = "wedding.jpg"
|
||||
UUID_NOT_FAVORITE = "1EB2B765-0765-43BA-A90C-0D0580E6172C"
|
||||
FILE_NOT_FAVORITE = "Pumpkins3.jpg"
|
||||
|
||||
|
||||
def modify_file(filename):
|
||||
"""appends data to a file to modify it"""
|
||||
@@ -2264,6 +2270,34 @@ def test_export_exiftool_merge_sidecar():
|
||||
assert exif[key] == CLI_EXIFTOOL_MERGE[uuid][key]
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool is None, reason="exiftool not installed")
|
||||
def test_export_exiftool_favorite_rating():
|
||||
"""Test --exiftol --favorite-rating"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
for uuid in CLI_EXIFTOOL:
|
||||
result = runner.invoke(
|
||||
export,
|
||||
[
|
||||
os.path.join(cwd, PHOTOS_DB_15_7),
|
||||
".",
|
||||
"-V",
|
||||
"--exiftool",
|
||||
"--uuid",
|
||||
UUID_FAVORITE,
|
||||
"--uuid",
|
||||
UUID_NOT_FAVORITE,
|
||||
"--favorite-rating",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert ExifTool(FILE_FAVORITE).asdict()["XMP:Rating"] == 5
|
||||
assert ExifTool(FILE_NOT_FAVORITE).asdict()["XMP:Rating"] == 0
|
||||
|
||||
|
||||
def test_export_edited_suffix():
|
||||
"""test export with --edited-suffix"""
|
||||
|
||||
@@ -3067,6 +3101,49 @@ def test_export_sidecar():
|
||||
assert sorted(files) == sorted(CLI_EXPORT_SIDECAR_FILENAMES)
|
||||
|
||||
|
||||
def test_export_sidecar_favorite_rating():
|
||||
"""test --sidecar --favorite-rating"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
cli_main,
|
||||
[
|
||||
"export",
|
||||
"--db",
|
||||
os.path.join(cwd, CLI_PHOTOS_DB),
|
||||
".",
|
||||
"--sidecar=json",
|
||||
"--sidecar=xmp",
|
||||
f"--uuid={UUID_FAVORITE}",
|
||||
f"--uuid={UUID_NOT_FAVORITE}",
|
||||
"--favorite-rating",
|
||||
"-V",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
with open(f"{FILE_FAVORITE}.json") as fp:
|
||||
json_sidecar = json.load(fp)
|
||||
assert json_sidecar[0]["XMP:Rating"] == 5
|
||||
with open(f"{FILE_NOT_FAVORITE}.json") as fp:
|
||||
json_sidecar = json.load(fp)
|
||||
assert json_sidecar[0]["XMP:Rating"] == 0
|
||||
|
||||
results = subprocess.run(
|
||||
["grep", "xmp:Rating", f"{FILE_FAVORITE}.xmp"], capture_output=True
|
||||
)
|
||||
results_stdout = results.stdout.decode("utf-8")
|
||||
assert "<xmp:Rating>5</xmp:Rating>" in results_stdout
|
||||
|
||||
results = subprocess.run(
|
||||
["grep", "xmp:Rating", f"{FILE_NOT_FAVORITE}.xmp"], capture_output=True
|
||||
)
|
||||
results_stdout = results.stdout.decode("utf-8")
|
||||
assert "<xmp:Rating>0</xmp:Rating>" in results_stdout
|
||||
|
||||
|
||||
def test_export_sidecar_drop_ext():
|
||||
"""test --sidecar with --sidecar-drop-ext option"""
|
||||
|
||||
@@ -5822,6 +5899,7 @@ def test_export_cleanup():
|
||||
"--dry-run",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Deleted: 2 files, 0 directories" in result.output
|
||||
assert pathlib.Path("./delete_me.txt").is_file()
|
||||
assert pathlib.Path("./foo/delete_me_too.txt").is_file()
|
||||
@@ -5836,6 +5914,35 @@ def test_export_cleanup():
|
||||
assert not pathlib.Path("./foo/delete_me_too.txt").is_file()
|
||||
|
||||
|
||||
def test_export_cleanup_report():
|
||||
"""test export with --cleanup flag with --report in the export dir (#739)"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
tmpdir = os.getcwd()
|
||||
|
||||
# run cleanup without dry-run
|
||||
result = runner.invoke(
|
||||
export,
|
||||
[
|
||||
os.path.join(cwd, CLI_PHOTOS_DB),
|
||||
".",
|
||||
"-V",
|
||||
"--update",
|
||||
"--cleanup",
|
||||
"--report",
|
||||
f"{tmpdir}/report.db",
|
||||
],
|
||||
)
|
||||
assert "Deleted: 0 files, 0 directories" in result.output
|
||||
assert pathlib.Path("./report.db").is_file()
|
||||
|
||||
|
||||
def test_export_cleanup_empty_album():
|
||||
"""test export with --cleanup flag with an empty album (#481)"""
|
||||
|
||||
@@ -5978,6 +6085,165 @@ def test_export_cleanup_exiftool_accented_album_name_same_filenames():
|
||||
assert "Deleted: 0 files, 0 directories" in result.output
|
||||
|
||||
|
||||
def test_export_cleanup_keep():
|
||||
"""test export with --cleanup --keep options"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
tmpdir = os.getcwd()
|
||||
result = runner.invoke(export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
# create file and a directory that should be deleted
|
||||
os.mkdir("./empty_dir")
|
||||
os.mkdir("./delete_me_dir")
|
||||
with open("./delete_me.txt", "w") as fd:
|
||||
fd.write("delete me!")
|
||||
with open("./delete_me_dir/delete_me.txt", "w") as fd:
|
||||
fd.write("delete me!")
|
||||
|
||||
# create files and directories that should be kept
|
||||
os.mkdir("./keep_me")
|
||||
os.mkdir("./keep_me/keep_me_2")
|
||||
with open("./keep_me.txt", "w") as fd:
|
||||
fd.write("keep me!")
|
||||
with open("./report.db", "w") as fd:
|
||||
fd.write("keep me!")
|
||||
with open("./keep_me/keep_me.txt", "w") as fd:
|
||||
fd.write("keep me")
|
||||
|
||||
# run cleanup with dry-run
|
||||
result = runner.invoke(
|
||||
export,
|
||||
[
|
||||
os.path.join(cwd, CLI_PHOTOS_DB),
|
||||
".",
|
||||
"-V",
|
||||
"--update",
|
||||
"--cleanup",
|
||||
"--keep",
|
||||
f"{tmpdir}/keep_me",
|
||||
"--keep",
|
||||
f"{tmpdir}/keep_me.txt",
|
||||
"--keep",
|
||||
f"{tmpdir}/*.db",
|
||||
"--dry-run",
|
||||
],
|
||||
)
|
||||
assert "Deleted: 2 files, 1 directory" in result.output
|
||||
assert pathlib.Path("./delete_me.txt").is_file()
|
||||
assert pathlib.Path("./delete_me_dir/delete_me.txt").is_file()
|
||||
assert pathlib.Path("./empty_dir").is_dir()
|
||||
|
||||
# run cleanup without dry-run
|
||||
result = runner.invoke(
|
||||
export,
|
||||
[
|
||||
os.path.join(cwd, CLI_PHOTOS_DB),
|
||||
".",
|
||||
"-V",
|
||||
"--update",
|
||||
"--cleanup",
|
||||
"--keep",
|
||||
f"{tmpdir}/keep_me",
|
||||
"--keep",
|
||||
f"{tmpdir}/keep_me.txt",
|
||||
"--keep",
|
||||
f"{tmpdir}/*.db",
|
||||
],
|
||||
)
|
||||
assert "Deleted: 2 files, 2 directories" in result.output
|
||||
assert not pathlib.Path("./delete_me.txt").is_file()
|
||||
assert not pathlib.Path("./delete_me_dir/delete_me_too.txt").is_file()
|
||||
assert not pathlib.Path("./empty_dir").is_dir()
|
||||
assert pathlib.Path("./keep_me.txt").is_file()
|
||||
assert pathlib.Path("./keep_me").is_dir()
|
||||
assert pathlib.Path("./keep_me/keep_me.txt").is_file()
|
||||
assert pathlib.Path("./keep_me/keep_me_2").is_dir()
|
||||
assert pathlib.Path("./report.db").is_file()
|
||||
|
||||
|
||||
def test_export_cleanup_keep_relative_path():
|
||||
"""test export with --cleanup --keep options with relative paths"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(export, [os.path.join(cwd, CLI_PHOTOS_DB), ".", "-V"])
|
||||
assert result.exit_code == 0
|
||||
|
||||
# create file and a directory that should be deleted
|
||||
os.mkdir("./empty_dir")
|
||||
os.mkdir("./delete_me_dir")
|
||||
with open("./delete_me.txt", "w") as fd:
|
||||
fd.write("delete me!")
|
||||
with open("./delete_me_dir/delete_me.txt", "w") as fd:
|
||||
fd.write("delete me!")
|
||||
|
||||
# create files and directories that should be kept
|
||||
os.mkdir("./keep_me")
|
||||
os.mkdir("./keep_me/keep_me_2")
|
||||
with open("./keep_me.txt", "w") as fd:
|
||||
fd.write("keep me!")
|
||||
with open("./report.db", "w") as fd:
|
||||
fd.write("keep me!")
|
||||
with open("./keep_me/keep_me.txt", "w") as fd:
|
||||
fd.write("keep me")
|
||||
|
||||
# run cleanup with dry-run
|
||||
result = runner.invoke(
|
||||
export,
|
||||
[
|
||||
os.path.join(cwd, CLI_PHOTOS_DB),
|
||||
".",
|
||||
"-V",
|
||||
"--update",
|
||||
"--cleanup",
|
||||
"--keep",
|
||||
"keep_me",
|
||||
"--keep",
|
||||
"keep_me.txt",
|
||||
"--keep",
|
||||
"*.db",
|
||||
"--dry-run",
|
||||
],
|
||||
)
|
||||
assert "Deleted: 2 files, 1 directory" in result.output
|
||||
assert pathlib.Path("./delete_me.txt").is_file()
|
||||
assert pathlib.Path("./delete_me_dir/delete_me.txt").is_file()
|
||||
assert pathlib.Path("./empty_dir").is_dir()
|
||||
|
||||
# run cleanup without dry-run
|
||||
result = runner.invoke(
|
||||
export,
|
||||
[
|
||||
os.path.join(cwd, CLI_PHOTOS_DB),
|
||||
".",
|
||||
"-V",
|
||||
"--update",
|
||||
"--cleanup",
|
||||
"--keep",
|
||||
"keep_me",
|
||||
"--keep",
|
||||
"keep_me.txt",
|
||||
"--keep",
|
||||
"*.db",
|
||||
],
|
||||
)
|
||||
assert "Deleted: 2 files, 2 directories" in result.output
|
||||
assert not pathlib.Path("./delete_me.txt").is_file()
|
||||
assert not pathlib.Path("./delete_me_dir/delete_me_too.txt").is_file()
|
||||
assert not pathlib.Path("./empty_dir").is_dir()
|
||||
assert pathlib.Path("./keep_me.txt").is_file()
|
||||
assert pathlib.Path("./keep_me").is_dir()
|
||||
assert pathlib.Path("./keep_me/keep_me.txt").is_file()
|
||||
assert pathlib.Path("./keep_me/keep_me_2").is_dir()
|
||||
assert pathlib.Path("./report.db").is_file()
|
||||
|
||||
|
||||
def test_save_load_config():
|
||||
"""test --save-config, --load-config"""
|
||||
|
||||
|
||||
910
tests/test_cli_import.py
Normal file
910
tests/test_cli_import.py
Normal file
@@ -0,0 +1,910 @@
|
||||
""" Tests which require user interaction to run for osxphotos import command; run with pytest --test-import """
|
||||
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import sqlite3
|
||||
import time
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Dict
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
from photoscript import Photo
|
||||
from pytest import approx
|
||||
|
||||
from osxphotos.cli.import_cli import import_cli
|
||||
from osxphotos.exiftool import get_exiftool_path
|
||||
from tests.conftest import get_os_version
|
||||
|
||||
TERMINAL_WIDTH = 250
|
||||
|
||||
TEST_IMAGES_DIR = "tests/test-images"
|
||||
TEST_IMAGE_1 = "tests/test-images/IMG_4179.jpeg"
|
||||
TEST_IMAGE_2 = "tests/test-images/faceinfo/exif1.jpg"
|
||||
TEST_VIDEO_1 = "tests/test-images/Jellyfish.mov"
|
||||
TEST_VIDEO_2 = "tests/test-images/IMG_0670B_NOGPS.MOV"
|
||||
|
||||
TEST_DATA = {
|
||||
TEST_IMAGE_1: {
|
||||
"title": "Waves crashing on rocks",
|
||||
"description": "Used for testing osxphotos",
|
||||
"keywords": ["osxphotos"],
|
||||
"lat": 33.7150638888889,
|
||||
"lon": -118.319672222222,
|
||||
"check_templates": [
|
||||
"exiftool title: Waves crashing on rocks",
|
||||
"exiftool description: Used for testing osxphotos",
|
||||
"exiftool keywords: ['osxphotos']",
|
||||
"exiftool location: (33.7150638888889, -118.319672222222)",
|
||||
"title: {exiftool:XMP:Title}: Waves crashing on rocks",
|
||||
"description: {exiftool:IPTC:Caption-Abstract}: Used for testing osxphotos",
|
||||
"keyword: {exiftool:IPTC:Keywords}: ['osxphotos']",
|
||||
"album: {filepath.parent}: test-images",
|
||||
],
|
||||
},
|
||||
TEST_VIDEO_1: {
|
||||
"title": "Jellyfish",
|
||||
"description": "Jellyfish Video",
|
||||
# "keywords": ["Travel"], # exiftool doesn't seem to support the binary QuickTime:Keywords
|
||||
"keywords": [],
|
||||
"lat": 34.0533,
|
||||
"lon": -118.2423,
|
||||
},
|
||||
TEST_VIDEO_2: {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"lat": None,
|
||||
"lon": None,
|
||||
},
|
||||
TEST_IMAGE_2: {
|
||||
"albums": ["faceinfo"],
|
||||
},
|
||||
}
|
||||
|
||||
# set timezone to avoid issues with comparing dates
|
||||
os.environ["TZ"] = "US/Pacific"
|
||||
time.tzset()
|
||||
|
||||
|
||||
# determine if exiftool installed so exiftool tests can be skipped
|
||||
try:
|
||||
exiftool_path = get_exiftool_path()
|
||||
except FileNotFoundError:
|
||||
exiftool_path = None
|
||||
|
||||
OS_VER = get_os_version()[1]
|
||||
if OS_VER != "15":
|
||||
pytest.skip(allow_module_level=True)
|
||||
|
||||
|
||||
def prompt(message):
|
||||
"""Helper function for tests that require user input"""
|
||||
message = f"\n{message}\nPlease answer y/n: "
|
||||
answer = input(message)
|
||||
return answer.lower() == "y"
|
||||
|
||||
|
||||
def say(msg: str) -> None:
|
||||
"""Say message with text to speech"""
|
||||
os.system(f"say {msg}")
|
||||
|
||||
|
||||
def parse_import_output(output: str) -> Dict[str, str]:
|
||||
"""Parse output of osxphotos import command and return dict of {image name: uuid} for imported photos"""
|
||||
# look for lines that look like this:
|
||||
# Imported IMG_4179.jpeg with UUID A62792F0-4524-4529-9931-56E52C95E873
|
||||
|
||||
results = {}
|
||||
for line in output.split("\n"):
|
||||
pattern = re.compile(
|
||||
r"Imported ([\w\.]+)\s.*UUID\s([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})"
|
||||
)
|
||||
if match := re.match(pattern, line):
|
||||
file = match[1]
|
||||
uuid = match[2]
|
||||
results[file] = uuid
|
||||
return results
|
||||
|
||||
|
||||
########## Interactive tests run first ##########
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import():
|
||||
"""Test basic import"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
["--verbose", test_image_1],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_dup_check():
|
||||
"""Test basic import with --dup-check"""
|
||||
say("Please click Import when prompted by Photos to import duplicate photo.")
|
||||
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
["--verbose", "--dup-check", test_image_1],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_album():
|
||||
"""Test basic import to an album"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
["--verbose", "--album", "My New Album", test_image_1],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
albums = photo_1.albums
|
||||
assert len(albums) == 1
|
||||
assert albums[0].title == "My New Album"
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_album_2():
|
||||
"""Test basic import to an album with a "/" in it"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
["--verbose", "--album", "Folder/My New Album", test_image_1],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
albums = photo_1.albums
|
||||
assert len(albums) == 1
|
||||
assert albums[0].title == "Folder/My New Album"
|
||||
assert albums[0].path_str() == "Folder/My New Album"
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_album_slit_folder():
|
||||
"""Test basic import to an album with a "/" in it and --split-folder"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--album",
|
||||
"Folder/My New Album",
|
||||
"--split-folder",
|
||||
"/",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
albums = photo_1.albums
|
||||
assert len(albums) == 1
|
||||
assert albums[0].title == "My New Album"
|
||||
assert albums[0].path_str() == "Folder/My New Album"
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_album_relative_to():
|
||||
"""Test import with --relative-to"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--album",
|
||||
"{filepath.parent}",
|
||||
"--split-folder",
|
||||
"/",
|
||||
"--relative-to",
|
||||
cwd,
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
albums = photo_1.albums
|
||||
assert len(albums) == 1
|
||||
assert albums[0].title == "test-images"
|
||||
assert albums[0].path_str() == "tests/test-images"
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_clear_metadata():
|
||||
"""Test import with --clear-metadata"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert not photo_1.title
|
||||
assert not photo_1.description
|
||||
assert not photo_1.keywords
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_exiftool():
|
||||
"""Test import file with --exiftool"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--exiftool",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert photo_1.title == TEST_DATA[TEST_IMAGE_1]["title"]
|
||||
assert photo_1.description == TEST_DATA[TEST_IMAGE_1]["description"]
|
||||
assert photo_1.keywords == TEST_DATA[TEST_IMAGE_1]["keywords"]
|
||||
lat, lon = photo_1.location
|
||||
assert lat == approx(TEST_DATA[TEST_IMAGE_1]["lat"])
|
||||
assert lon == approx(TEST_DATA[TEST_IMAGE_1]["lon"])
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_exiftool_video():
|
||||
"""Test import video file with --exiftool"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_VIDEO_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--exiftool",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert photo_1.title == TEST_DATA[TEST_VIDEO_1]["title"]
|
||||
assert photo_1.description == TEST_DATA[TEST_VIDEO_1]["description"]
|
||||
assert photo_1.keywords == TEST_DATA[TEST_VIDEO_1]["keywords"]
|
||||
lat, lon = photo_1.location
|
||||
assert lat == approx(TEST_DATA[TEST_VIDEO_1]["lat"])
|
||||
assert lon == approx(TEST_DATA[TEST_VIDEO_1]["lon"])
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_exiftool_video_no_metadata():
|
||||
"""Test import video file with --exiftool that has no metadata"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_VIDEO_2)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--exiftool",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert photo_1.title == ""
|
||||
assert photo_1.description == ""
|
||||
assert photo_1.keywords == []
|
||||
lat, lon = photo_1.location
|
||||
assert lat is None
|
||||
assert lon is None
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_title():
|
||||
"""Test import with --title"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--title",
|
||||
"{exiftool:XMP:Title|upper}",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert photo_1.title == TEST_DATA[TEST_IMAGE_1]["title"].upper()
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_description():
|
||||
"""Test import with --description"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--description",
|
||||
"{exiftool:XMP:Description|upper}",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert photo_1.description == TEST_DATA[TEST_IMAGE_1]["description"].upper()
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_keyword():
|
||||
"""Test import with --keyword"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--keyword",
|
||||
"Bar",
|
||||
"--keyword",
|
||||
"Foo",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert sorted(photo_1.keywords) == ["Bar", "Foo"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_keyword_merge():
|
||||
"""Test import with --keyword and --merge-keywords"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--exiftool",
|
||||
"--keyword",
|
||||
"Bar",
|
||||
"--keyword",
|
||||
"Foo",
|
||||
"--merge-keywords",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert sorted(photo_1.keywords) == ["Bar", "Foo", "osxphotos"]
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_location():
|
||||
"""Test import file with --location"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--clear-metadata",
|
||||
"--location",
|
||||
"-45.0",
|
||||
"-45.0",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image_1).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
lat, lon = photo_1.location
|
||||
assert lat == approx(-45.0)
|
||||
assert lon == approx(-45.0)
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_glob():
|
||||
"""Test import with --glob"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
["--verbose", f"{cwd}/{TEST_IMAGES_DIR}/", "--walk", "--glob", "Pumpk*.jpg"],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "imported 2 files" in result.output
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_glob_walk():
|
||||
"""Test import with --walk --glob"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
f"{cwd}/{TEST_IMAGES_DIR}/",
|
||||
"--walk",
|
||||
"--glob",
|
||||
"exif*.jpg",
|
||||
"--album",
|
||||
"{filepath.parent.name}",
|
||||
"--relative-to",
|
||||
f"{cwd}/{TEST_IMAGES_DIR}",
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "imported 4 files" in result.output
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(TEST_IMAGE_2).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
assert [a.title for a in photo_1.albums] == TEST_DATA[TEST_IMAGE_2]["albums"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(exiftool_path is None, reason="exiftool not installed")
|
||||
@pytest.mark.test_import
|
||||
def test_import_check_templates():
|
||||
"""Test import file with --check-templates"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--exiftool",
|
||||
"--title",
|
||||
"{exiftool:XMP:Title}",
|
||||
"--description",
|
||||
"{exiftool:IPTC:Caption-Abstract}",
|
||||
"--keyword",
|
||||
"{exiftool:IPTC:Keywords}",
|
||||
"--album",
|
||||
"{filepath.parent}",
|
||||
"--relative-to",
|
||||
f"{cwd}/tests",
|
||||
"--check-templates",
|
||||
test_image_1,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
# assert result.output == "foo"
|
||||
assert result.exit_code == 0
|
||||
output = result.output.splitlines()
|
||||
output.pop(0)
|
||||
|
||||
for idx, line in enumerate(output):
|
||||
assert line == TEST_DATA[TEST_IMAGE_1]["check_templates"][idx]
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_function_template():
|
||||
"""Test import with a function template"""
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
function = os.path.join(cwd, "examples/template_function_import.py")
|
||||
with TemporaryDirectory() as tempdir:
|
||||
test_image = shutil.copy(
|
||||
test_image_1, os.path.join(tempdir, "MyAlbum_IMG_0001.jpg")
|
||||
)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
"--verbose",
|
||||
"--album",
|
||||
"{function:" + function + "::example}",
|
||||
test_image,
|
||||
],
|
||||
terminal_width=TERMINAL_WIDTH,
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
import_data = parse_import_output(result.output)
|
||||
file_1 = pathlib.Path(test_image).name
|
||||
uuid_1 = import_data[file_1]
|
||||
photo_1 = Photo(uuid_1)
|
||||
|
||||
assert photo_1.filename == file_1
|
||||
albums = [a.title for a in photo_1.albums]
|
||||
assert albums == ["MyAlbum"]
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_report():
|
||||
"""test import with --report option"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
test_image_1,
|
||||
"--report",
|
||||
"report.csv",
|
||||
"--verbose",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Wrote import report" in result.output
|
||||
assert os.path.exists("report.csv")
|
||||
with open("report.csv", "r") as f:
|
||||
reader = csv.DictReader(f)
|
||||
rows = list(reader)
|
||||
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
|
||||
assert filenames == [pathlib.Path(TEST_IMAGE_1).name]
|
||||
|
||||
# test report gets overwritten
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
test_image_1,
|
||||
"--report",
|
||||
"report.csv",
|
||||
"--verbose",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
with open("report.csv", "r") as f:
|
||||
reader = csv.DictReader(f)
|
||||
rows = list(reader)
|
||||
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
|
||||
assert filenames == [pathlib.Path(TEST_IMAGE_1).name]
|
||||
|
||||
# test report with --append
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
test_image_1,
|
||||
"--report",
|
||||
"report.csv",
|
||||
"--append",
|
||||
"--verbose",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
with open("report.csv", "r") as f:
|
||||
reader = csv.DictReader(f)
|
||||
rows = list(reader)
|
||||
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
|
||||
assert filenames == [
|
||||
pathlib.Path(TEST_IMAGE_1).name,
|
||||
pathlib.Path(TEST_IMAGE_1).name,
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_report_json():
|
||||
"""test import with --report option with json output"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
test_image_1,
|
||||
"--report",
|
||||
"report.json",
|
||||
"--verbose",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Wrote import report" in result.output
|
||||
assert os.path.exists("report.json")
|
||||
with open("report.json", "r") as f:
|
||||
rows = json.load(f)
|
||||
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
|
||||
assert filenames == [pathlib.Path(TEST_IMAGE_1).name]
|
||||
|
||||
# test report gets overwritten
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
test_image_1,
|
||||
"--report",
|
||||
"report.json",
|
||||
"--verbose",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Wrote import report" in result.output
|
||||
assert os.path.exists("report.json")
|
||||
with open("report.json", "r") as f:
|
||||
rows = json.load(f)
|
||||
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
|
||||
assert filenames == [pathlib.Path(TEST_IMAGE_1).name]
|
||||
|
||||
# test report with --append
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
test_image_1,
|
||||
"--report",
|
||||
"report.json",
|
||||
"--append",
|
||||
"--verbose",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Wrote import report" in result.output
|
||||
assert os.path.exists("report.json")
|
||||
with open("report.json", "r") as f:
|
||||
rows = json.load(f)
|
||||
filenames = [str(pathlib.Path(row["filename"]).name) for row in rows]
|
||||
assert filenames == [
|
||||
pathlib.Path(TEST_IMAGE_1).name,
|
||||
pathlib.Path(TEST_IMAGE_1).name,
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
@pytest.mark.parametrize("report_file", ["report.db", "report.sqlite"])
|
||||
def test_import_report_sqlite(report_file):
|
||||
"""test import with --report option with sqlite output"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
test_image_1,
|
||||
"--report",
|
||||
report_file,
|
||||
"--verbose",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Wrote import report" in result.output
|
||||
assert os.path.exists(report_file)
|
||||
conn = sqlite3.connect(report_file)
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT filename FROM report")
|
||||
filenames = [str(pathlib.Path(row[0]).name) for row in c.fetchall()]
|
||||
assert filenames == [pathlib.Path(TEST_IMAGE_1).name]
|
||||
|
||||
# test report gets overwritten
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
test_image_1,
|
||||
"--report",
|
||||
report_file,
|
||||
"--verbose",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Wrote import report" in result.output
|
||||
assert os.path.exists(report_file)
|
||||
conn = sqlite3.connect(report_file)
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT filename FROM report")
|
||||
filenames = [str(pathlib.Path(row[0]).name) for row in c.fetchall()]
|
||||
assert filenames == [pathlib.Path(TEST_IMAGE_1).name]
|
||||
|
||||
# test report with --append
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
test_image_1,
|
||||
"--report",
|
||||
report_file,
|
||||
"--append",
|
||||
"--verbose",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Wrote import report" in result.output
|
||||
assert os.path.exists(report_file)
|
||||
conn = sqlite3.connect(report_file)
|
||||
c = conn.cursor()
|
||||
c.execute("SELECT filename FROM report")
|
||||
filenames = [str(pathlib.Path(row[0]).name) for row in c.fetchall()]
|
||||
assert filenames == [
|
||||
pathlib.Path(TEST_IMAGE_1).name,
|
||||
pathlib.Path(TEST_IMAGE_1).name,
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.test_import
|
||||
def test_import_report_invalid_name():
|
||||
"""test import with --report option with invalid report"""
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
import_cli,
|
||||
[
|
||||
test_image_1,
|
||||
"--report",
|
||||
"report", # invalid filename, no extension
|
||||
"--verbose",
|
||||
],
|
||||
)
|
||||
assert result.exit_code != 0
|
||||
35
tests/test_cli_orphans.py
Normal file
35
tests/test_cli_orphans.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""Test `osxphotos orphan` CLI"""
|
||||
|
||||
import os.path
|
||||
|
||||
from click.testing import CliRunner
|
||||
|
||||
from osxphotos.cli.orphans import orphans
|
||||
|
||||
from .test_cli import PHOTOS_DB_15_7
|
||||
|
||||
|
||||
def test_orphans():
|
||||
"""test basic orphans"""
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
orphans, ["--db", os.path.join(cwd, PHOTOS_DB_15_7), "-V"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Found 1 orphan" in result.output
|
||||
|
||||
|
||||
def test_orphans_export():
|
||||
"""test export of orphans"""
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
result = runner.invoke(
|
||||
orphans, ["--db", os.path.join(cwd, PHOTOS_DB_15_7), "--export", ".", "-V"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "Exported 1 file" in result.output
|
||||
30
tests/test_sqlite_utils.py
Normal file
30
tests/test_sqlite_utils.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Tests for sqlite_utils """
|
||||
|
||||
import pytest
|
||||
import sqlite3
|
||||
|
||||
from osxphotos.sqlite_utils import sqlite_open_ro, sqlite_db_is_locked
|
||||
|
||||
|
||||
DB_LOCKED_10_12 = "./tests/Test-Lock-10_12.photoslibrary/database/photos.db"
|
||||
DB_LOCKED_10_15 = "./tests/Test-Lock-10_15_1.photoslibrary/database/Photos.sqlite"
|
||||
DB_UNLOCKED_10_15 = "./tests/Test-10.15.1.photoslibrary/database/photos.db"
|
||||
|
||||
|
||||
def test_db_is_locked_locked():
|
||||
|
||||
assert sqlite_db_is_locked(DB_LOCKED_10_12)
|
||||
assert sqlite_db_is_locked(DB_LOCKED_10_15)
|
||||
|
||||
|
||||
def test_db_is_locked_unlocked():
|
||||
|
||||
assert not sqlite_db_is_locked(DB_UNLOCKED_10_15)
|
||||
|
||||
|
||||
def test_open_sqlite_ro():
|
||||
|
||||
conn, cur = sqlite_open_ro(DB_UNLOCKED_10_15)
|
||||
assert isinstance(conn, sqlite3.Connection)
|
||||
assert isinstance(cur, sqlite3.Cursor)
|
||||
conn.close()
|
||||
@@ -238,6 +238,21 @@ TEMPLATE_VALUES = {
|
||||
"{descr|autosplit|slice(:2)|join()}": "JackRose",
|
||||
"{descr|autosplit|slice(:-1)|join()}": "JackRoseDining",
|
||||
"{descr|autosplit|slice(::2)|join()}": "JackDining",
|
||||
"{descr|filter(startswith Jack)}": "Jack Rose Dining Saloon",
|
||||
"{descr|filter(startswith Rose)}": "_",
|
||||
"{descr|filter(endswith Saloon)}": "Jack Rose Dining Saloon",
|
||||
"{descr|filter(endswith Rose)}": "_",
|
||||
"{descr|filter(contains Rose)}": "Jack Rose Dining Saloon",
|
||||
"{descr|filter(not contains Rose)}": "_",
|
||||
"{descr|filter(matches Jack Rose Dining Saloon)}": "Jack Rose Dining Saloon",
|
||||
"{created.mm|filter(== 02)}": "02",
|
||||
"{created.mm|filter(<= 2)}": "02",
|
||||
"{created.mm|filter(>= 2)}": "02",
|
||||
"{created.mm|filter(> 3)}": "_",
|
||||
"{created.mm|filter(< 1)}": "_",
|
||||
"{created.mm|filter(!= 02)}": "_",
|
||||
"{created.mm|int|filter(== 2)}": "2",
|
||||
"{created.mm|float|filter(== 2.0)}": "2.0",
|
||||
}
|
||||
|
||||
|
||||
@@ -437,6 +452,7 @@ UUID_ALBUM_SEQ = {
|
||||
"{folder_album_seq(1)}": "2",
|
||||
"{folder_album_seq(0)}": "1",
|
||||
"{folder_album_seq:03d(1)}": "002",
|
||||
"{folder_album|filter(startswith Folder1)}": "Folder1/SubFolder2/AlbumInFolder",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1228,6 +1244,9 @@ def test_filepath():
|
||||
rendered, _ = template.render("{filepath.parent}", options)
|
||||
assert rendered[0] == "/foo"
|
||||
|
||||
rendered, _ = template.render("{filepath.parent.name}", options)
|
||||
assert rendered[0] == "foo"
|
||||
|
||||
rendered, _ = template.render("{filepath.stem}", options)
|
||||
assert rendered[0] == "bar"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
import os.path
|
||||
"""Test osxphotos.utils"""
|
||||
|
||||
import pathlib
|
||||
import tempfile
|
||||
|
||||
@@ -7,16 +7,12 @@ import pytest
|
||||
|
||||
import osxphotos
|
||||
|
||||
DB_LOCKED_10_12 = "./tests/Test-Lock-10_12.photoslibrary/database/photos.db"
|
||||
DB_LOCKED_10_15 = "./tests/Test-Lock-10_15_1.photoslibrary/database/Photos.sqlite"
|
||||
DB_UNLOCKED_10_15 = "./tests/Test-10.15.1.photoslibrary/database/photos.db"
|
||||
|
||||
UTI_DICT = {"public.jpeg": "jpeg", "com.canon.cr2-raw-image": "cr2"}
|
||||
|
||||
from osxphotos.utils import (
|
||||
_dd_to_dms,
|
||||
increment_filename_with_count,
|
||||
increment_filename,
|
||||
increment_filename_with_count,
|
||||
list_directory,
|
||||
shortuuid_to_uuid,
|
||||
uuid_to_shortuuid,
|
||||
@@ -39,17 +35,6 @@ def test_get_system_library_path():
|
||||
assert osxphotos.utils.get_system_library_path() is not None
|
||||
|
||||
|
||||
def test_db_is_locked_locked():
|
||||
|
||||
assert osxphotos.utils._db_is_locked(DB_LOCKED_10_12)
|
||||
assert osxphotos.utils._db_is_locked(DB_LOCKED_10_15)
|
||||
|
||||
|
||||
def test_db_is_locked_unlocked():
|
||||
|
||||
assert not osxphotos.utils._db_is_locked(DB_UNLOCKED_10_15)
|
||||
|
||||
|
||||
def test_list_directory():
|
||||
"""test list_directory"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user