Compare commits

..

31 Commits

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

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

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

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

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

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

* Refactored

* Improved help, added --clear-metadata

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

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

* Added --location

* Added test for --location

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

* Added --walk, updated docs

* Added --check-templates

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

* Implemented orphans command, #84
2022-08-13 08:43:02 -07:00
Rhet Turnbull
2103d8bcad Added bad_photos example [skip ci] 2022-08-11 22:06:33 -07:00
Rhet Turnbull
26a9028497 Add PhotosAlbumPhotosKit to __all__ 2022-08-10 21:37:33 -06:00
Rhet Turnbull
e41f89480a Add PhotosAlbum to osxphotos __all__ 2022-08-10 20:53:07 -06:00
63 changed files with 4726 additions and 174 deletions

View File

@@ -363,6 +363,26 @@
"contributions": [
"bug"
]
},
{
"login": "jmuccigr",
"name": "John Muccigrosso",
"avatar_url": "https://avatars.githubusercontent.com/u/615115?v=4",
"profile": "http://jmuccigr.github.io/",
"contributions": [
"bug",
"ideas"
]
},
{
"login": "tkrunning",
"name": "Thomas K. Running",
"avatar_url": "https://avatars.githubusercontent.com/u/1646041?v=4",
"profile": "https://nomadgate.com",
"contributions": [
"code",
"bug"
]
}
],
"contributorsPerLine": 7,

8
.bumpversion.cfg Normal file
View File

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

View File

@@ -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"]`:
@@ -2004,8 +2007,9 @@ cog.out(get_template_field_table())
|{newline}|A newline: '\n'|
|{lf}|A line feed: '\n', alias for {newline}|
|{cr}|A carriage return: '\r'|
|{crlf}|a carriage return + line feed: '\r\n'|
|{osxphotos_version}|The osxphotos version, e.g. '0.50.12'|
|{crlf}|A carriage return + line feed: '\r\n'|
|{tab}|:A tab: '\t'|
|{osxphotos_version}|The osxphotos version, e.g. '0.51.4'|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|{album}|Album(s) photo is contained in|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|

View File

@@ -4,6 +4,67 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v0.51.4](https://github.com/RhetTbull/osxphotos/compare/v0.51.3...v0.51.4)
> 27 August 2022
- Release 0.51.4, added --print to dump [`bb480f6`](https://github.com/RhetTbull/osxphotos/commit/bb480f69914ed351b6f4309b0f6aa539add1a9fb)
- Added --print to dump, added {tab} [`5eaeb72`](https://github.com/RhetTbull/osxphotos/commit/5eaeb72c3ee296af6abc6ca6ddf8ad05baf02052)
- Fixed --print to work with {tab} [`af9311c`](https://github.com/RhetTbull/osxphotos/commit/af9311c9c86a3d0a5764ebd1539d40f14e62f2ec)
#### [v0.51.3](https://github.com/RhetTbull/osxphotos/compare/v0.51.2...v0.51.3)
> 27 August 2022
- Added --print, --quiet, #769, #770 [`#773`](https://github.com/RhetTbull/osxphotos/pull/773)
- Release 0.51.3, added --print (#769), --quiet (#770) [`d576ca5`](https://github.com/RhetTbull/osxphotos/commit/d576ca54948c0cbbd16f04231459a294f0333f89)
- Added bump2version [`320fb86`](https://github.com/RhetTbull/osxphotos/commit/320fb8655980882fd75e354d33450f0904babf2a)
#### [v0.51.2](https://github.com/RhetTbull/osxphotos/compare/v0.51.1...v0.51.2)
> 26 August 2022
- Release 0.51.2, added new filter(x) to template filters [`#772`](https://github.com/RhetTbull/osxphotos/pull/772)
- Feature filter filter 759 [`#771`](https://github.com/RhetTbull/osxphotos/pull/771)
#### [v0.51.1](https://github.com/RhetTbull/osxphotos/compare/v0.51.0...v0.51.1)
> 22 August 2022
- Release 0.51.1, added --report to import [`#767`](https://github.com/RhetTbull/osxphotos/pull/767)
- Added --report to import command [`#766`](https://github.com/RhetTbull/osxphotos/pull/766)
- Fixed template function to work with import command [`#765`](https://github.com/RhetTbull/osxphotos/pull/765)
- Updated README [skip ci] [`b5f4c48`](https://github.com/RhetTbull/osxphotos/commit/b5f4c48ec98b344b5d7ed7c2a5e9a445d322df13)
#### [v0.51.0](https://github.com/RhetTbull/osxphotos/compare/v0.50.13...v0.51.0)
> 21 August 2022
- Release 0.51.0 [`#763`](https://github.com/RhetTbull/osxphotos/pull/763)
- Feature add import 754 [`#762`](https://github.com/RhetTbull/osxphotos/pull/762)
- Updated tested versions [`#757`](https://github.com/RhetTbull/osxphotos/pull/757)
- Updated examples [skip ci] [`c7e3a55`](https://github.com/RhetTbull/osxphotos/commit/c7e3a552db60321fc9999153b6b5624bc1bb76dc)
- Updated xmp_rating example [`1e053aa`](https://github.com/RhetTbull/osxphotos/commit/1e053aa7086af44a207d1be045d698c7d10b97f5)
- Updated xmp_rating example [`46738d0`](https://github.com/RhetTbull/osxphotos/commit/46738d05b213d1d8ef390add71142623892385ce)
#### [v0.50.13](https://github.com/RhetTbull/osxphotos/compare/v0.50.12...v0.50.13)
> 13 August 2022
- Release 0.50.13 [`#756`](https://github.com/RhetTbull/osxphotos/pull/756)
- Feature orphans [`#755`](https://github.com/RhetTbull/osxphotos/pull/755)
- Added bad_photos example [skip ci] [`2103d8b`](https://github.com/RhetTbull/osxphotos/commit/2103d8bcad65807d23f14315dbc4a76b3b6badfe)
- Add PhotosAlbumPhotosKit to __all__ [`26a9028`](https://github.com/RhetTbull/osxphotos/commit/26a9028497d147c047a4b73c5cf0fe964f7b2e00)
- Add PhotosAlbum to osxphotos __all__ [`e41f894`](https://github.com/RhetTbull/osxphotos/commit/e41f89480a37a19778c7b97fa0c8b0ceedfd56cd)
#### [v0.50.12](https://github.com/RhetTbull/osxphotos/compare/v0.50.11...v0.50.12)
> 8 August 2022
- Hot fix for 749 [`#750`](https://github.com/RhetTbull/osxphotos/pull/750)
- Added strip_live.py example [`#747`](https://github.com/RhetTbull/osxphotos/pull/747)
- Added reddit badge for r/osxphotos [`#746`](https://github.com/RhetTbull/osxphotos/pull/746)
#### [v0.50.11](https://github.com/RhetTbull/osxphotos/compare/v0.50.10...v0.50.11)
> 28 July 2022

View File

@@ -7,7 +7,7 @@
[![Downloads](https://static.pepy.tech/personalized-badge/osxphotos?period=month&units=international_system&left_color=black&right_color=brightgreen&left_text=downloads/month)](https://pepy.tech/project/osxphotos)
[![subreddit](https://img.shields.io/reddit/subreddit-subscribers/osxphotos?style=social)](https://www.reddit.com/r/osxphotos/)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-38-orange.svg?style=flat)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-40-orange.svg?style=flat)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
OSXPhotos provides the ability to interact with and query Apple's Photos.app library on macOS. You can query the Photos library database — for example, file name, file path, and metadata such as keywords/tags, persons/faces, albums, etc. You can also easily export both the original and edited photos.
@@ -142,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...
@@ -264,6 +266,22 @@ the exported files would be:
/path/to/export/Travel/IMG_1234.JPG
/path/to/export/Vacation/IMG_1234.JPG
If your photos are organized in folders and albums in Photos you can preserve this structure on export by using the `{folder_album}` template field with the `--directory` option. For example, if you have a photo in the album `Vacation` which is in the `Travel` folder, the following command would export the photo to the `Travel/Vacation` directory:
`osxphotos export /path/to/export --directory "{folder_album}"`
Photos can belong to more than one album. In this case, the template field `{folder_album}` will expand to all the album names that the photo belongs to. For example, if a photo belongs to the albums `Vacation` and `Travel`, the template field `{folder_album}` would expand to `Vacation`, `Travel`. If the photo belongs to no albums, the template field `{folder_album}` would expand to "_" (the default value).
All template fields including `{folder_album}` can be further filtered using a number of different filters. To convert all directory names to lower case for example, use the `lower` filter:
`osxphotos export /path/to/export --directory "{folder_album|lower}"`
If all your photos were organized into various albums under a folder named `Events` but some where also included in other top-level albums and you wanted to export only the `Events` folder, you could use the `filter` option to filter out the other top-level albums by selecting only those folder/album paths that start with `Events`:
`osxphotos export /path/to/export --directory "{folder_album|filter(startswith Events)}"`
You can learn more about the other filters using `osxphotos help export`.
#### Specify exported filename
By default, osxphotos will use the original filename of the photo when exporting. That is, the filename the photo had when it was taken or imported into Photos. This is often something like `IMG_1234.JPG` or `DSC05678.dng`. osxphotos allows you to specify a custom filename template using the `--filename` option in the same way as `--directory` allows you to specify a custom directory name. For example, Photos allows you specify a title or caption for a photo and you can use this in place of the original filename:
@@ -443,6 +461,14 @@ You can use the `--report` option to create a report, in comma-separated values
`osxphotos export /path/to/export --report export.csv`
You can also create reports in JSON or SQLite format by changing the extension of the report filename. For example, to create a JSON report:
`osxphotos export /path/to/export --report export.json`
And to create a SQLite report:
`osxphotos export /path/to/export --report export.sqlite`
#### Exporting only certain photos
By default, osxphotos will export your entire Photos library. If you want to export only certain photos, osxphotos provides a rich set of "query options" that allow you to query the Photos database to filter out only certain photos that match your query criteria. The tutorial does not cover all the query options as there are over 50 of them--read the help text (`osxphotos help export`) to better understand the available query options. No matter which subset of photos you would like to export, there is almost certainly a way for osxphotos to filter these. For example, you can filter for only images that contain certain keywords or images without a title, images from a specific time of day or specific date range, images contained in specific albums, etc.
@@ -1315,6 +1341,13 @@ Options:
--config-only If specified, saves the config file but does
not export any files; must be used with
--save-config.
--print TEMPLATE Render TEMPLATE string for each photo being
exported and print to stdout. TEMPLATE is an
osxphotos template string. This may be useful
for creating custom reports, etc. TEMPLATE
will be printed after the photo is exported or
skipped. May be repeated to print multiple
template strings.
--theme THEME Specify the color theme to use for --verbose
output. Valid themes are 'dark', 'light',
'mono', and 'plain'. Defaults to 'dark' or
@@ -1525,6 +1558,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"]:
@@ -1952,8 +1994,9 @@ Substitution Description
{newline} A newline: '\n'
{lf} A line feed: '\n', alias for {newline}
{cr} A carriage return: '\r'
{crlf} a carriage return + line feed: '\r\n'
{osxphotos_version} The osxphotos version, e.g. '0.50.12'
{crlf} A carriage return + line feed: '\r\n'
{tab} :A tab: '\t'
{osxphotos_version} The osxphotos version, e.g. '0.51.4'
{osxphotos_cmd_line} The full command line used to run osxphotos
The following substitutions may result in multiple values. Thus if specified
@@ -2243,6 +2286,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"]`:
@@ -2429,8 +2475,9 @@ The following template field substitutions are availabe for use the templating s
|{newline}|A newline: '\n'|
|{lf}|A line feed: '\n', alias for {newline}|
|{cr}|A carriage return: '\r'|
|{crlf}|a carriage return + line feed: '\r\n'|
|{osxphotos_version}|The osxphotos version, e.g. '0.50.12'|
|{crlf}|A carriage return + line feed: '\r\n'|
|{tab}|:A tab: '\t'|
|{osxphotos_version}|The osxphotos version, e.g. '0.51.4'|
|{osxphotos_cmd_line}|The full command line used to run osxphotos|
|{album}|Album(s) photo is contained in|
|{folder_album}|Folder path + album photo is contained in. e.g. 'Folder/Subfolder/Album' or just 'Album' if no enclosing folder|
@@ -2529,6 +2576,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/infused-kim"><img src="https://avatars.githubusercontent.com/u/7404004?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Kim</b></sub></a><br /><a href="#ideas-infused-kim" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/Se7enair"><img src="https://avatars.githubusercontent.com/u/1680106?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Christoph</b></sub></a><br /><a href="#ideas-Se7enair" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://www.franzone.com"><img src="https://avatars.githubusercontent.com/u/900684?v=4?s=75" width="75px;" alt=""/><br /><sub><b>franzone</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Afranzone" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://jmuccigr.github.io/"><img src="https://avatars.githubusercontent.com/u/615115?v=4?s=75" width="75px;" alt=""/><br /><sub><b>John Muccigrosso</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Ajmuccigr" title="Bug reports">🐛</a> <a href="#ideas-jmuccigr" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://nomadgate.com"><img src="https://avatars.githubusercontent.com/u/1646041?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Thomas K. Running</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/commits?author=tkrunning" title="Code">💻</a> <a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Atkrunning" title="Bug reports">🐛</a></td>
</tr>
</table>

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos._constants - osxphotos 0.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>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.exiftool - osxphotos 0.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">

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.photoexporter - osxphotos 0.50.9 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.9 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.9 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">

View File

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

View File

@@ -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_process_comments - osxphotos 0.50.7 documentation</title>
<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" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../../index.html"><div class="brand">osxphotos 0.50.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.50.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">

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../../genindex.html" /><link rel="search" title="Search" href="../../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.photosdb.photosdb - osxphotos 0.50.12 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.12 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.12 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">

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.phototemplate - osxphotos 0.50.3 documentation</title>
<title>osxphotos.phototemplate - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="../../_static/copybutton.css" />
@@ -123,7 +123,7 @@
</label>
</div>
<div class="header-center">
<a href="../../index.html"><div class="brand">osxphotos 0.50.3 documentation</div></a>
<a href="../../index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -146,7 +146,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="../../index.html">
<span class="sidebar-brand-text">osxphotos 0.50.3 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="../../search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -377,7 +377,8 @@
<span class="s2">"</span><span class="si">{newline}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">"A newline: '\n'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{lf}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">"A line feed: '\n', alias for </span><span class="si">{newline}</span><span class="s2">"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{cr}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">"A carriage return: '\r'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{crlf}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">"a carriage return + line feed: '\r\n'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{crlf}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">"A carriage return + line feed: '\r\n'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{tab}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">r</span><span class="s2">":A tab: '\t'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{osxphotos_version}</span><span class="s2">"</span><span class="p">:</span> <span class="sa">f</span><span class="s2">"The osxphotos version, e.g. '</span><span class="si">{</span><span class="n">__version__</span><span class="si">}</span><span class="s2">'"</span><span class="p">,</span>
<span class="s2">"</span><span class="si">{osxphotos_cmd_line}</span><span class="s2">"</span><span class="p">:</span> <span class="s2">"The full command line used to run osxphotos"</span><span class="p">,</span>
<span class="p">}</span>
@@ -464,6 +465,11 @@
<span class="o">+</span> <span class="s2">"slice(::-1): ['a', 'b', 'c', 'd'] =&gt; ['d', 'c', 'b', 'a']. See also sslice()."</span><span class="p">,</span>
<span class="s2">"sslice(start:stop:step)"</span><span class="p">:</span> <span class="s2">"[s(tring) slice] Slice values in a list using same semantics as Python's string slicing, "</span>
<span class="o">+</span> <span class="s2">"e.g. sslice(1:3):'abcd =&gt; 'bc'; sslice(1:4:2): 'abcd' =&gt; 'bd', etc. See also slice()."</span><span class="p">,</span>
<span class="s2">"filter(x)"</span><span class="p">:</span> <span class="s2">"Filter list of values using predicate x; for example, `{folder_album|filter(contains Events)}` returns only folders/albums containing the word 'Events' in their path."</span><span class="p">,</span>
<span class="s2">"int"</span><span class="p">:</span> <span class="s2">"Convert values in list to integer, e.g. 1.0 =&gt; 1. If value cannot be converted to integer, remove value from list. "</span>
<span class="o">+</span> <span class="s2">"['1.1', 'x'] =&gt; ['1']. See also float."</span><span class="p">,</span>
<span class="s2">"float"</span><span class="p">:</span> <span class="s2">"Convert values in list to floating point number, e.g. 1 =&gt; 1.0. If value cannot be converted to float, remove value from list. "</span>
<span class="o">+</span> <span class="s2">"['1', 'x'] =&gt; ['1.0']. See also int."</span><span class="p">,</span>
<span class="p">}</span>
<span class="c1"># Just the substitutions without the braces</span>
@@ -503,6 +509,7 @@
<span class="s2">"lf"</span><span class="p">:</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span>
<span class="s2">"cr"</span><span class="p">:</span> <span class="s2">"</span><span class="se">\r</span><span class="s2">"</span><span class="p">,</span>
<span class="s2">"crlf"</span><span class="p">:</span> <span class="s2">"</span><span class="se">\r\n</span><span class="s2">"</span><span class="p">,</span>
<span class="s2">"tab"</span><span class="p">:</span> <span class="s2">"</span><span class="se">\t</span><span class="s2">"</span><span class="p">,</span>
<span class="p">}</span>
@@ -525,6 +532,7 @@
<span class="sd"> dest_path: set to the destination path of the photo (for use by {function} template), only valid with --filename</span>
<span class="sd"> filepath: set to value for filepath of the exported photo if you want to evaluate {filepath} template</span>
<span class="sd"> quote: quote path templates for execution in the shell</span>
<span class="sd"> caller: which command is calling the template (e.g. 'export')</span>
<span class="sd"> """</span>
<span class="n">none_str</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">"_"</span>
@@ -539,6 +547,7 @@
<span class="n">dest_path</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">filepath</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">quote</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">caller</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">"export"</span>
<span class="k">class</span> <span class="nc">PhotoTemplateParser</span><span class="p">:</span>
@@ -861,16 +870,18 @@
<span class="n">vals</span> <span class="o">=</span> <span class="n">string_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="n">c</span><span class="p">))</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s2">"=="</span><span class="p">:</span>
<span class="n">match</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">vals</span><span class="p">)</span> <span class="o">==</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">conditional_value</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">):</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">)</span>
<span class="k">else</span> <span class="p">[]</span>
<span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s2">"!="</span><span class="p">:</span>
<span class="n">match</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">vals</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">conditional_value</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">):</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">vals</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">[</span><span class="s2">"True"</span><span class="p">]</span>
<span class="k">if</span> <span class="p">(</span><span class="n">match</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">negation</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">negation</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">match</span><span class="p">)</span>
<span class="k">else</span> <span class="p">[]</span>
<span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s2">"&lt;"</span><span class="p">:</span>
<span class="n">vals</span> <span class="o">=</span> <span class="n">comparison_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span> <span class="o">&lt;</span> <span class="n">c</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">operator</span> <span class="o">==</span> <span class="s2">"&lt;="</span><span class="p">:</span>
@@ -984,7 +995,9 @@
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
<span class="s2">"SyntaxError: filename and function must not be null with {function::filename.py:function_name}"</span>
<span class="p">)</span>
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_function</span><span class="p">(</span><span class="n">subfield</span><span class="p">,</span> <span class="n">field_arg</span><span class="p">)</span>
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_function</span><span class="p">(</span>
<span class="n">subfield</span><span class="p">,</span> <span class="n">field_arg</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="o">.</span><span class="n">caller</span>
<span class="p">)</span>
<span class="k">elif</span> <span class="n">field</span> <span class="ow">in</span> <span class="n">MULTI_VALUE_SUBSTITUTIONS</span> <span class="ow">or</span> <span class="n">field</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"photo"</span><span class="p">):</span>
<span class="n">vals</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_template_value_multi</span><span class="p">(</span>
<span class="n">field</span><span class="p">,</span> <span class="n">subfield</span><span class="p">,</span> <span class="n">path_sep</span><span class="o">=</span><span class="n">field_arg</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">default</span>
@@ -1374,6 +1387,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 +1476,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">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
<span class="sd">"""Return True if value passes predicate"""</span>
<span class="c1"># extract function name and arguments</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">args</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="s2">"Filter predicate requires arguments"</span><span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="kc">None</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">if</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"not"</span><span class="p">:</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="k">return</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">filter_predicate</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="s2">" "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">args</span><span class="p">))</span>
<span class="n">predicate</span> <span class="o">=</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">conditional_value</span> <span class="o">=</span> <span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"|"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">comparison_test</span><span class="p">(</span><span class="n">test_function</span><span class="p">):</span>
<span class="sd">"""Perform numerical comparisons using test_function"""</span>
<span class="c1"># returns True if any of the values match the condition</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="nb">any</span><span class="p">(</span>
<span class="nb">bool</span><span class="p">(</span><span class="n">test_function</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">value</span><span class="p">),</span> <span class="nb">float</span><span class="p">(</span><span class="n">c</span><span class="p">)))</span>
<span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span>
<span class="p">)</span>
<span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span>
<span class="sa">f</span><span class="s2">"comparison operators may only be used with values that can be converted to numbers: </span><span class="si">{</span><span class="n">vals</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">conditional_value</span><span class="si">}</span><span class="s2">"</span>
<span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">if</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"contains"</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">c</span> <span class="ow">in</span> <span class="n">value</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"endswith"</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">value</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="ow">in</span> <span class="p">[</span><span class="s2">"matches"</span><span class="p">,</span> <span class="s2">"=="</span><span class="p">]:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">value</span> <span class="o">==</span> <span class="n">c</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"startswith"</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">value</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"!="</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="nb">any</span><span class="p">(</span><span class="n">value</span> <span class="o">!=</span> <span class="n">c</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">conditional_value</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"&lt;"</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="n">comparison_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span> <span class="o">&lt;</span> <span class="n">c</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"&lt;="</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="n">comparison_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span> <span class="o">&lt;=</span> <span class="n">c</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"&gt;"</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="n">comparison_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span> <span class="o">&gt;</span> <span class="n">c</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">predicate</span> <span class="o">==</span> <span class="s2">"&gt;="</span><span class="p">:</span>
<span class="n">predicate_is_true</span> <span class="o">=</span> <span class="n">comparison_test</span><span class="p">(</span><span class="k">lambda</span> <span class="n">v</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="n">v</span> <span class="o">&gt;=</span> <span class="n">c</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">SyntaxError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Invalid predicate: </span><span class="si">{</span><span class="n">predicate</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">predicate_is_true</span></div>
<div class="viewcode-block" id="PhotoTemplate.get_template_value_multi"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_template_value_multi">[docs]</a> <span class="k">def</span> <span class="nf">get_template_value_multi</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">field</span><span class="p">,</span> <span class="n">subfield</span><span class="p">,</span> <span class="n">path_sep</span><span class="p">,</span> <span class="n">default</span><span class="p">):</span>
<span class="sd">"""lookup value for template field (multi-value template substitutions)</span>
@@ -1655,10 +1729,17 @@
<div class="viewcode-block" id="PhotoTemplate.get_template_value_function"><a class="viewcode-back" href="../../reference.html#osxphotos.PhotoTemplate.get_template_value_function">[docs]</a> <span class="k">def</span> <span class="nf">get_template_value_function</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span>
<span class="n">subfield</span><span class="p">,</span>
<span class="n">field_arg</span><span class="p">,</span>
<span class="n">subfield</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="n">field_arg</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span>
<span class="n">caller</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span>
<span class="p">):</span>
<span class="sd">"""Get template value from external function"""</span>
<span class="sd">"""Get template value from external function</span>
<span class="sd"> Args:</span>
<span class="sd"> subfield: the filename and function name in for filename.py::function</span>
<span class="sd"> field_arg: the argument to pass to the function</span>
<span class="sd"> caller: the calling source of the template ('export' or 'import')</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="s2">"::"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">subfield</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
@@ -1677,8 +1758,17 @@
<span class="c1"># if no uuid, then template is being validated but not actually run</span>
<span class="c1"># so don't run the function</span>
<span class="n">values</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">elif</span> <span class="n">caller</span> <span class="o">==</span> <span class="s2">"export"</span><span class="p">:</span>
<span class="c1"># function signature is:</span>
<span class="c1"># def example(photo: PhotoInfo, options: ExportOptions, args: Optional[str] = None, **kwargs) -&gt; Union[List, str]:</span>
<span class="n">values</span> <span class="o">=</span> <span class="n">template_func</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">options</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="n">field_arg</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">caller</span> <span class="o">==</span> <span class="s2">"import"</span><span class="p">:</span>
<span class="c1"># function signature is:</span>
<span class="c1"># def example(filepath: pathlib.Path, args: Optional[str] = None, **kwargs) -&gt; Union[List, str]:</span>
<span class="c1"># the PhotoInfoFromFile class used by import sets `path` to the path of the file being imported</span>
<span class="n">values</span> <span class="o">=</span> <span class="n">template_func</span><span class="p">(</span><span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">photo</span><span class="o">.</span><span class="n">path</span><span class="p">),</span> <span class="n">args</span><span class="o">=</span><span class="n">field_arg</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Unhandled caller: </span><span class="si">{</span><span class="n">caller</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">values</span><span class="p">,</span> <span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="nb">list</span><span class="p">)):</span>
<span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span>
@@ -1825,20 +1915,18 @@
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">return</span> <span class="n">shlex</span><span class="o">.</span><span class="n">quote</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">if</span> <span class="n">quote</span> <span class="k">else</span> <span class="n">value</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">parts</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">2</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Illegal value for path template: </span><span class="si">{</span><span class="n">field</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">attribute</span> <span class="o">=</span> <span class="n">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">val</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">attribute</span><span class="p">)</span>
<span class="n">val_str</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
<span class="k">if</span> <span class="n">quote</span><span class="p">:</span>
<span class="n">val_str</span> <span class="o">=</span> <span class="n">shlex</span><span class="o">.</span><span class="n">quote</span><span class="p">(</span><span class="n">val_str</span><span class="p">)</span>
<span class="k">return</span> <span class="n">val_str</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"Illegal value for path template: </span><span class="si">{attribute}</span><span class="s2">"</span><span class="p">)</span>
<span class="k">for</span> <span class="n">attribute</span> <span class="ow">in</span> <span class="n">parts</span><span class="p">[</span><span class="mi">1</span><span class="p">:]:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">val</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">attribute</span><span class="p">)</span>
<span class="n">path</span> <span class="o">=</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">AttributeError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Illegal value for filepath template: </span><span class="si">{</span><span class="n">attribute</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="kn">from</span> <span class="nn">e</span>
<span class="n">val_str</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
<span class="k">if</span> <span class="n">quote</span><span class="p">:</span>
<span class="n">val_str</span> <span class="o">=</span> <span class="n">shlex</span><span class="o">.</span><span class="n">quote</span><span class="p">(</span><span class="n">val_str</span><span class="p">)</span>
<span class="k">return</span> <span class="n">val_str</span>
<span class="k">def</span> <span class="nf">format_str_value</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">format_str</span><span class="p">):</span>
@@ -1908,6 +1996,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">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="sd">"""Convert a list of strings to str representation of ints, if possible, otherwise strip values from list"""</span>
<span class="n">int_values</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">:</span>
<span class="k">with</span> <span class="n">suppress</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">):</span>
<span class="n">int_values</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">v</span><span class="p">))))</span>
<span class="k">return</span> <span class="n">int_values</span>
<span class="k">def</span> <span class="nf">values_to_float</span><span class="p">(</span><span class="n">values</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="sd">"""Convert a list of strings to str representation of float, if possible, otherwise strip values from list"""</span>
<span class="n">float_values</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">:</span>
<span class="k">with</span> <span class="n">suppress</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">):</span>
<span class="n">float_values</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="nb">float</span><span class="p">(</span><span class="n">v</span><span class="p">)))</span>
<span class="k">return</span> <span class="n">float_values</span>
</pre></div>
</article>
</div>

View File

@@ -5,7 +5,7 @@
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="../../genindex.html" /><link rel="search" title="Search" href="../../search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>osxphotos.queryoptions - osxphotos 0.50.11 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.50.11 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.11 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">

View File

@@ -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"]``\ :
@@ -344,9 +347,11 @@ Template Substitutions
* - {cr}
- A carriage return: '\r'
* - {crlf}
- a carriage return + line feed: '\r\n'
- A carriage return + line feed: '\r\n'
* - {tab}
- :A tab: '\t'
* - {osxphotos_version}
- The osxphotos version, e.g. '0.50.12'
- The osxphotos version, e.g. '0.51.4'
* - {osxphotos_cmd_line}
- The full command line used to run osxphotos
* - {album}

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.50.12 documentation</title>
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/><title>Index - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -122,7 +122,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.12 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -145,7 +145,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.12 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -269,6 +269,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-album">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-a">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-album">osxphotos-query command line option</a>
</li>
@@ -293,6 +295,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-append">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-append">osxphotos-exportdb command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-append">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -311,6 +315,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-check-signatures">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
--check-templates
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-check-templates">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -318,6 +329,20 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-cleanup">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--clear-location
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-L">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
--clear-metadata
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-C">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -408,6 +433,8 @@
<li><a href="cli.html#cmdoption-osxphotos-keywords-db">osxphotos-keywords command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-labels-db">osxphotos-labels command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-db">osxphotos-orphans command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-persons-db">osxphotos-persons command line option</a>
</li>
@@ -486,6 +513,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-description">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-d">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-description">osxphotos-query command line option</a>
</li>
@@ -531,6 +560,13 @@
<li><a href="cli.html#cmdoption-osxphotos-export-dry-run">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-dry-run">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
--dup-check
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-D">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -592,6 +628,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-exiftool">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-e">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -628,8 +666,17 @@
<li><a href="cli.html#cmdoption-osxphotos-exiftool-exiftool-path">osxphotos-exiftool command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-exiftool-path">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-p">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-e">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
--export
<ul>
<li><a href="cli.html#cmdoption-osxphotos-orphans-export">osxphotos-orphans command line option</a>
</li>
</ul></li>
<li>
@@ -766,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>
@@ -953,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>
@@ -1032,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>
@@ -1054,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>
@@ -1167,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>
@@ -1180,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
@@ -1213,6 +1278,8 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-not-favorite">osxphotos-repl command line option</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
--not-hdr
@@ -1508,6 +1575,17 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-preview-suffix">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--print
<ul>
<li><a href="cli.html#cmdoption-osxphotos-dump-print">osxphotos-dump command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-print">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-print">osxphotos-query command line option</a>
</li>
</ul></li>
<li>
@@ -1544,6 +1622,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-query-function">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-query-function">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--quiet
<ul>
<li><a href="cli.html#cmdoption-osxphotos-query-quiet">osxphotos-query command line option</a>
</li>
</ul></li>
<li>
@@ -1569,6 +1654,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-regex">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-regex">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--relative-to
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-r">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -1589,6 +1681,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-report">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-report">osxphotos-exportdb command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-report">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -1732,6 +1826,13 @@
<li><a href="cli.html#cmdoption-osxphotos-query-slow-mo">osxphotos-query command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-slow-mo">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
--split-folder
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-f">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -1769,8 +1870,12 @@
<li><a href="cli.html#cmdoption-osxphotos-exiftool-theme">osxphotos-exiftool command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-theme">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-theme">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-theme">osxphotos-inspect command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-theme">osxphotos-orphans command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-theme">osxphotos-timewarp command line option</a>
</li>
@@ -1807,6 +1912,10 @@
<li><a href="cli.html#cmdoption-osxphotos-exiftool-timestamp">osxphotos-exiftool command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-timestamp">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-T">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-timestamp">osxphotos-orphans command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-timestamp">osxphotos-timewarp command line option</a>
</li>
@@ -1823,6 +1932,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-title">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-t">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-title">osxphotos-query command line option</a>
</li>
@@ -1974,6 +2085,10 @@
<li><a href="cli.html#cmdoption-osxphotos-export-V">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-V">osxphotos-exportdb command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-V">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-V">osxphotos-orphans command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-V">osxphotos-timewarp command line option</a>
</li>
@@ -1985,6 +2100,13 @@
<li><a href="cli.html#cmdoption-osxphotos-v">osxphotos command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-version">osxphotos-exportdb command line option</a>
</li>
</ul></li>
<li>
--walk
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-w">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -2016,7 +2138,16 @@
-a
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-a">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-a">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
-C
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-C">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -2030,6 +2161,8 @@
-D
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-D">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-D">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2037,6 +2170,8 @@
-d
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-d">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-d">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2044,6 +2179,8 @@
-e
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-e">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-e">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2058,9 +2195,18 @@
-f
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-f">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-0">osxphotos-timewarp command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-uuid-f">osxphotos-uuid command line option</a>
</li>
</ul></li>
<li>
-g
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-g">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -2081,19 +2227,37 @@
<li><a href="cli.html#cmdoption-osxphotos-repl-i">osxphotos-repl command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-i">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
-k
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-k">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
-L
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-L">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-L">osxphotos-timewarp command line option</a>
</li>
</ul></li>
<li>
-l
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-l">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
-m
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-m">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-m">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2115,6 +2279,8 @@
-p
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-p">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-p">osxphotos-timewarp command line option</a>
</li>
</ul></li>
@@ -2123,6 +2289,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-diff-r">osxphotos-diff command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-r">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -2136,6 +2304,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>
@@ -2145,6 +2315,8 @@
-t
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-t">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-inspect-t">osxphotos-inspect command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-t">osxphotos-timewarp command line option</a>
@@ -2168,6 +2340,10 @@
<li><a href="cli.html#cmdoption-osxphotos-export-V">osxphotos-export command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-exportdb-V">osxphotos-exportdb command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-V">osxphotos-import command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-orphans-V">osxphotos-orphans command line option</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-timewarp-V">osxphotos-timewarp command line option</a>
</li>
@@ -2177,6 +2353,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-v">osxphotos command line option</a>
</li>
</ul></li>
<li>
-w
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-w">osxphotos-import command line option</a>
</li>
</ul></li>
<li>
@@ -2512,13 +2695,22 @@
</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>
@@ -2972,6 +3164,8 @@
<li><a href="cli.html#cmdoption-osxphotos-dump-deleted-only">--deleted-only</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-dump-json">--json</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-dump-print">--print</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-dump-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
</li>
@@ -3240,6 +3434,8 @@
<li><a href="cli.html#cmdoption-osxphotos-export-preview-if-missing">--preview-if-missing</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-preview-suffix">--preview-suffix</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-print">--print</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-export-query-eval">--query-eval</a>
</li>
@@ -3384,6 +3580,91 @@
<li><a href="cli.html#cmdoption-osxphotos-help-arg-SUBTOPIC">SUBTOPIC</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-help-arg-TOPIC">TOPIC</a>
</li>
</ul></li>
<li>
osxphotos-import command line option
<ul>
<li><a href="cli.html#cmdoption-osxphotos-import-a">--album</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-append">--append</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-check-templates">--check-templates</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-L">--clear-location</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-C">--clear-metadata</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-d">--description</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-D">--dup-check</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-e">--exiftool</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-p">--exiftool-path</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-g">--glob</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-k">--keyword</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-l">--location</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-m">--merge-keywords</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-no-progress">--no-progress</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-r">--relative-to</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-report">--report</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-f">--split-folder</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-theme">--theme</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-T">--timestamp</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-t">--title</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-V">--verbose</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-w">--walk</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-a">-a</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-C">-C</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-d">-d</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-D">-D</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-e">-e</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-f">-f</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-g">-g</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-k">-k</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-l">-l</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-L">-L</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-m">-m</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-p">-p</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-r">-r</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-t">-t</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-T">-T</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-V">-V</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-w">-w</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-import-arg-FILES">FILES</a>
</li>
</ul></li>
<li>
@@ -3397,6 +3678,8 @@
<li><a href="cli.html#cmdoption-osxphotos-info-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
osxphotos-inspect command line option
@@ -3436,8 +3719,6 @@
<li><a href="cli.html#cmdoption-osxphotos-keywords-arg-PHOTOS_LIBRARY">PHOTOS_LIBRARY</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li>
osxphotos-labels command line option
@@ -3454,6 +3735,23 @@
<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>
@@ -3615,10 +3913,14 @@
<li><a href="cli.html#cmdoption-osxphotos-query-place">--place</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-portrait">--portrait</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-print">--print</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-query-eval">--query-eval</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-query-function">--query-function</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-quiet">--quiet</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-query-regex">--regex</a>
</li>
@@ -4094,6 +4396,10 @@
<li><a href="cli.html#cmdoption-osxphotos-query-arg-PHOTOS_LIBRARY">osxphotos-query command line option</a>
</li>
</ul></li>
<li><a href="reference.html#osxphotos.PhotosAlbum">PhotosAlbum (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.PhotosAlbumPhotoScript">PhotosAlbumPhotoScript (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.PhotosDB">PhotosDB (class in osxphotos)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoTemplate">PhotoTemplate (class in osxphotos)</a>

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="OSXPhotos Python Package Overview" href="package_overview.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>OSXPhotos python API - osxphotos 0.50.12 documentation</title>
<title>OSXPhotos python API - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.12 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.12 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -1844,6 +1844,11 @@ Enforce that the expanded value is a single value, raises ValueError if not.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="osxphotos.PhotoTemplate.filter_predicate">
<span class="sig-name descname"><span class="pre">filter_predicate</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">value</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">args</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon"></span> <span class="sig-return-typehint"><span class="pre">bool</span></span></span><a class="reference internal" href="_modules/osxphotos/phototemplate.html#PhotoTemplate.filter_predicate"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoTemplate.filter_predicate" title="Permalink to this definition">#</a></dt>
<dd><p>Return True if value passes predicate</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="osxphotos.PhotoTemplate.get_field_values">
<span class="sig-name descname"><span class="pre">get_field_values</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">field</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">subfield</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">field_arg</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">default</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon"></span> <span class="sig-return-typehint"><span class="pre">Tuple</span><span class="p"><span class="pre">[</span></span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">List</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span></span><a class="reference internal" href="_modules/osxphotos/phototemplate.html#PhotoTemplate.get_field_values"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoTemplate.get_field_values" title="Permalink to this definition">#</a></dt>
<dd><p>Get the values for a field</p>
@@ -1908,8 +1913,17 @@ Enforce that the expanded value is a single value, raises ValueError if not.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="osxphotos.PhotoTemplate.get_template_value_function">
<span class="sig-name descname"><span class="pre">get_template_value_function</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">subfield</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">field_arg</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/phototemplate.html#PhotoTemplate.get_template_value_function"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoTemplate.get_template_value_function" title="Permalink to this definition">#</a></dt>
<span class="sig-name descname"><span class="pre">get_template_value_function</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">subfield</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">field_arg</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span></em>, <em class="sig-param"><span class="n"><span class="pre">caller</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/phototemplate.html#PhotoTemplate.get_template_value_function"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotoTemplate.get_template_value_function" title="Permalink to this definition">#</a></dt>
<dd><p>Get template value from external function</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>subfield</strong> the filename and function name in for filename.py::function</p></li>
<li><p><strong>field_arg</strong> the argument to pass to the function</p></li>
<li><p><strong>caller</strong> the calling source of the template (export or import)</p></li>
</ul>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="osxphotos.PhotoTemplate.get_template_value_multi">
@@ -1969,6 +1983,16 @@ Enforce that the expanded value is a single value, raises ValueError if not.</p>
</dd></dl>
</dd></dl>
<dl class="py class">
<dt class="sig sig-object py" id="osxphotos.PhotosAlbum">
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">osxphotos.</span></span><span class="sig-name descname"><span class="pre">PhotosAlbum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">name</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbose</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">callable</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photosalbum.html#PhotosAlbum"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosAlbum" title="Permalink to this definition">#</a></dt>
<dd><p>Add osxphotos.photoinfo.PhotoInfo objects to album</p>
</dd></dl>
<dl class="py class">
<dt class="sig sig-object py" id="osxphotos.PhotosAlbumPhotoScript">
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">osxphotos.</span></span><span class="sig-name descname"><span class="pre">PhotosAlbumPhotoScript</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">name</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbose</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">callable</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">split_folder</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">str</span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photosalbum.html#PhotosAlbumPhotoScript"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosAlbumPhotoScript" title="Permalink to this definition">#</a></dt>
<dd><p>Add photoscript.Photo objects to album</p>
</dd></dl>
<dl class="py class">
<dt class="sig sig-object py" id="osxphotos.PhotosDB">
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">osxphotos.</span></span><span class="sig-name descname"><span class="pre">PhotosDB</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">dbfile</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbose</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">exiftool</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">rich</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photosdb/photosdb.html#PhotosDB"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosDB" title="Permalink to this definition">#</a></dt>
<dd><p>Processes a Photos.app library database to extract information about photos</p>

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="OSXPhotos Python Package Overview" href="package_overview.html" /><link rel="prev" title="OSXPhotos Command Line Interface (CLI)" href="cli.html" />
<meta name="generator" content="sphinx-4.4.0, furo 2022.04.07"/>
<title>OSXPhotos Template System - osxphotos 0.50.12 documentation</title>
<title>OSXPhotos Template System - osxphotos 0.51.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=68f4518137b9aefe99b631505a2064c3c42c9852" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -124,7 +124,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">osxphotos 0.50.12 documentation</div></a>
<a href="index.html"><div class="brand">osxphotos 0.51.4 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -147,7 +147,7 @@
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<span class="sidebar-brand-text">osxphotos 0.50.12 documentation</span>
<span class="sidebar-brand-text">osxphotos 0.51.4 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -241,6 +241,9 @@
<li><p><cite>remove(x)</cite>: Remove x from list of values, e.g. remove(b): [a, b, c] =&gt; [a, c].</p></li>
<li><p><cite>slice(start:stop:step)</cite>: Slice list using same semantics as Pythons list slicing, e.g. slice(1:3): [a, b, c, d] =&gt; [b, c]; slice(1:4:2): [a, b, c, d] =&gt; [b, d]; slice(1:): [a, b, c, d] =&gt; [b, c, d]; slice(:-1): [a, b, c, d] =&gt; [a, b, c]; slice(::-1): [a, b, c, d] =&gt; [d, c, b, a]. See also sslice().</p></li>
<li><p><cite>sslice(start:stop:step)</cite>: [s(tring) slice] Slice values in a list using same semantics as Pythons string slicing, e.g. sslice(1:3):abcd =&gt; bc; sslice(1:4:2): abcd =&gt; bd, etc. See also slice().</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">filter(x)</span></code>: Filter list of values using predicate x; for example, <code class="docutils literal notranslate"><span class="pre">{folder_album|filter(contains</span> <span class="pre">Events)}</span></code> returns only folders/albums containing the word Events in their path.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">int</span></code>: Convert values in list to integer, e.g. 1.0 =&gt; 1. If value cannot be converted to integer, remove value from list. [1.1, x] =&gt; [1]. See also float.</p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">float</span></code>: Convert values in list to floating point number, e.g. 1 =&gt; 1.0. If value cannot be converted to float, remove value from list. [1, x] =&gt; [1.0]. See also int.</p></li>
</ul>
<p>e.g. if Photo keywords are <code class="docutils literal notranslate"><span class="pre">["FOO","bar"]</span></code>:</p>
<ul class="simple">
@@ -586,75 +589,83 @@
<td><p>A carriage return: r</p></td>
</tr>
<tr class="row-odd"><td><p>{crlf}</p></td>
<td><p>a carriage return + line feed: rn</p></td>
<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.12</p></td>
<tr class="row-even"><td><p>{tab}</p></td>
<td><dl class="field-list simple">
<dt class="field-odd">A tab</dt>
<dd class="field-odd"><p>t</p>
</dd>
</dl>
</td>
</tr>
<tr class="row-odd"><td><p>{osxphotos_cmd_line}</p></td>
<tr class="row-odd"><td><p>{osxphotos_version}</p></td>
<td><p>The osxphotos version, e.g. 0.51.4</p></td>
</tr>
<tr class="row-even"><td><p>{osxphotos_cmd_line}</p></td>
<td><p>The full command line used to run osxphotos</p></td>
</tr>
<tr class="row-even"><td><p>{album}</p></td>
<tr class="row-odd"><td><p>{album}</p></td>
<td><p>Album(s) photo is contained in</p></td>
</tr>
<tr class="row-odd"><td><p>{folder_album}</p></td>
<tr class="row-even"><td><p>{folder_album}</p></td>
<td><p>Folder path + album photo is contained in. e.g. Folder/Subfolder/Album or just Album if no enclosing folder</p></td>
</tr>
<tr class="row-even"><td><p>{project}</p></td>
<tr class="row-odd"><td><p>{project}</p></td>
<td><p>Project(s) photo is contained in (such as greeting cards, calendars, slideshows)</p></td>
</tr>
<tr class="row-odd"><td><p>{album_project}</p></td>
<tr class="row-even"><td><p>{album_project}</p></td>
<td><p>Album(s) and project(s) photo is contained in; treats projects as regular albums</p></td>
</tr>
<tr class="row-even"><td><p>{folder_album_project}</p></td>
<tr class="row-odd"><td><p>{folder_album_project}</p></td>
<td><p>Folder path + album (includes projects as albums) photo is contained in. e.g. Folder/Subfolder/Album or just Album if no enclosing folder</p></td>
</tr>
<tr class="row-odd"><td><p>{keyword}</p></td>
<tr class="row-even"><td><p>{keyword}</p></td>
<td><p>Keyword(s) assigned to photo</p></td>
</tr>
<tr class="row-even"><td><p>{person}</p></td>
<tr class="row-odd"><td><p>{person}</p></td>
<td><p>Person(s) / face(s) in a photo</p></td>
</tr>
<tr class="row-odd"><td><p>{label}</p></td>
<tr class="row-even"><td><p>{label}</p></td>
<td><p>Image categorization label associated with a photo (Photos 5+ only). Labels are added automatically by Photos using machine learning algorithms to categorize images. These are not the same as {keyword} which refers to the user-defined keywords/tags applied in Photos.</p></td>
</tr>
<tr class="row-even"><td><p>{label_normalized}</p></td>
<tr class="row-odd"><td><p>{label_normalized}</p></td>
<td><p>All lower case version of label (Photos 5+ only)</p></td>
</tr>
<tr class="row-odd"><td><p>{comment}</p></td>
<tr class="row-even"><td><p>{comment}</p></td>
<td><p>Comment(s) on shared Photos; format is Person name: comment text (Photos 5+ only)</p></td>
</tr>
<tr class="row-even"><td><p>{exiftool}</p></td>
<tr class="row-odd"><td><p>{exiftool}</p></td>
<td><p>Format: {exiftool:GROUP:TAGNAME}; use exiftool (https://exiftool.org) to extract metadata, in form GROUP:TAGNAME, from image. E.g. {exiftool:EXIF:Make} to get camera make, or {exiftool:IPTC:Keywords} to extract keywords. See https://exiftool.org/TagNames/ for list of valid tag names. You must specify group (e.g. EXIF, IPTC, etc) as used in <code class="docutils literal notranslate"><span class="pre">exiftool</span> <span class="pre">-G</span></code>. exiftool must be installed in the path to use this template.</p></td>
</tr>
<tr class="row-odd"><td><p>{searchinfo.holiday}</p></td>
<tr class="row-even"><td><p>{searchinfo.holiday}</p></td>
<td><p>Holiday names associated with a photo, e.g. Christmas Day; (Photos 5+ only, applied automatically by Photos image categorization algorithms).</p></td>
</tr>
<tr class="row-even"><td><p>{searchinfo.activity}</p></td>
<tr class="row-odd"><td><p>{searchinfo.activity}</p></td>
<td><p>Activities associated with a photo, e.g. Sporting Event; (Photos 5+ only, applied automatically by Photos image categorization algorithms).</p></td>
</tr>
<tr class="row-odd"><td><p>{searchinfo.venue}</p></td>
<tr class="row-even"><td><p>{searchinfo.venue}</p></td>
<td><p>Venues associated with a photo, e.g. name of restaurant; (Photos 5+ only, applied automatically by Photos image categorization algorithms).</p></td>
</tr>
<tr class="row-even"><td><p>{searchinfo.venue_type}</p></td>
<tr class="row-odd"><td><p>{searchinfo.venue_type}</p></td>
<td><p>Venue types associated with a photo, e.g. Restaurant; (Photos 5+ only, applied automatically by Photos image categorization algorithms).</p></td>
</tr>
<tr class="row-odd"><td><p>{photo}</p></td>
<tr class="row-even"><td><p>{photo}</p></td>
<td><p>Provides direct access to the PhotoInfo object for the photo. Must be used in format {photo.property} where property represents a PhotoInfo property. For example: {photo.favorite} is the same as {favorite} and {photo.place.name} is the same as {place.name}. {photo} provides access to properties that are not available as separate template fields but it assumes some knowledge of the underlying PhotoInfo class. See <a class="reference external" href="https://rhettbull.github.io/osxphotos/">https://rhettbull.github.io/osxphotos/</a> for additional documentation on the PhotoInfo class.</p></td>
</tr>
<tr class="row-even"><td><p>{detected_text}</p></td>
<tr class="row-odd"><td><p>{detected_text}</p></td>
<td><p>List of text strings found in the image after performing text detection. Using {detected_text} will cause osxphotos to perform text detection on your photos using the built-in macOS text detection algorithms which will slow down your export. The results for each photo will be cached in the export database so that future exports with update do not need to reprocess each photo. You may pass a confidence threshold value between 0.0 and 1.0 after a colon as in {detected_text:0.5}; The default confidence threshold is 0.75. {detected_text} works only on macOS Catalina (10.15) or later. Note: this feature is not the same thing as Live Text in macOS Monterey, which osxphotos does not yet support.</p></td>
</tr>
<tr class="row-odd"><td><p>{shell_quote}</p></td>
<tr class="row-even"><td><p>{shell_quote}</p></td>
<td><p>Use in form {shell_quote,TEMPLATE}; quotes the rendered TEMPLATE value(s) for safe usage in the shell, e.g. My file.jpeg =&gt; My file.jpeg; only adds quotes if needed.</p></td>
</tr>
<tr class="row-even"><td><p>{strip}</p></td>
<tr class="row-odd"><td><p>{strip}</p></td>
<td><p>Use in form {strip,TEMPLATE}; strips whitespace from begining and end of rendered TEMPLATE value(s).</p></td>
</tr>
<tr class="row-odd"><td><p>{format}</p></td>
<tr class="row-even"><td><p>{format}</p></td>
<td><p>Use in form, {format:TYPE:FORMAT,TEMPLATE}; converts TEMPLATE value to TYPE then formats the value using Python string formatting codes specified by FORMAT; TYPE is one of: int, float, or str. For example, {format:float:.1f,{exiftool:EXIF:FocalLength}} will format focal length to 1 decimal place (e.g. 100.0).</p></td>
</tr>
<tr class="row-even"><td><p>{function}</p></td>
<tr class="row-odd"><td><p>{function}</p></td>
<td><p>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.</p></td>
</tr>
</tbody>

View File

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

View File

@@ -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"]``\ :
@@ -344,9 +347,11 @@ Template Substitutions
* - {cr}
- A carriage return: '\r'
* - {crlf}
- a carriage return + line feed: '\r\n'
- A carriage return + line feed: '\r\n'
* - {tab}
- :A tab: '\t'
* - {osxphotos_version}
- The osxphotos version, e.g. '0.50.12'
- The osxphotos version, e.g. '0.51.4'
* - {osxphotos_cmd_line}
- The full command line used to run osxphotos
* - {album}

View File

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

43
examples/bad_photos.py Normal file
View File

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

View File

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

93
examples/xmp_rating.py Normal file
View File

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

View File

@@ -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",

View File

@@ -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

View File

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

View File

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

View File

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

View File

@@ -35,6 +35,7 @@ __all__ = [
"DB_OPTION",
"DEBUG_OPTIONS",
"DELETED_OPTIONS",
"FIELD_OPTION",
"JSON_OPTION",
"QUERY_OPTIONS",
"THEME_OPTION",
@@ -116,6 +117,19 @@ JSON_OPTION = click.option(
help="Print output in JSON format.",
)
FIELD_OPTION = click.option(
"--field",
"-f",
metavar="FIELD TEMPLATE",
multiple=True,
nargs=2,
help="Output only specified custom fields. "
"FIELD is the name of the field and TEMPLATE is the template to use as the field value. "
"May be repeated to output multiple fields. "
"For example, to output photo uuid, name, and title: "
'`--field uuid "{uuid}" --field name "{original_name}" --field title "{title}"`.',
)
def DELETED_OPTIONS(f):
o = click.option

View File

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

View File

@@ -674,6 +674,17 @@ from .verbose import get_verbose_console, time_stamp, verbose_print
is_flag=True,
help="If specified, saves the config file but does not export any files; must be used with --save-config.",
)
@click.option(
"--print",
"print_template",
metavar="TEMPLATE",
multiple=True,
help="Render TEMPLATE string for each photo being exported and print to stdout. "
"TEMPLATE is an osxphotos template string. "
"This may be useful for creating custom reports, etc. "
"TEMPLATE will be printed after the photo is exported or skipped. "
"May be repeated to print multiple template strings. ",
)
@click.option(
"--beta",
is_flag=True,
@@ -826,6 +837,7 @@ def export(
preview_if_missing,
preview_suffix,
preview,
print_template,
profile_sort,
profile,
query_eval,
@@ -1048,6 +1060,7 @@ def export(
preview = cfg.preview
preview_if_missing = cfg.preview_if_missing
preview_suffix = cfg.preview_suffix
print_template = cfg.print_template
query_eval = cfg.query_eval
query_function = cfg.query_function
ramdb = cfg.ramdb
@@ -1652,6 +1665,22 @@ def export(
report_writer.write(export_results)
if print_template:
options = RenderOptions(export_dir=dest)
for template in print_template:
rendered_templates, unmatched = p.render_template(
template,
options,
)
if unmatched:
rich_click_echo(
f"[warning]Unmatched template field: {unmatched}[/]"
)
for rendered_template in rendered_templates:
if not rendered_template:
continue
rich_click_echo(rendered_template)
progress.advance(task)
# handle limit

1352
osxphotos/cli/import_cli.py Normal file

File diff suppressed because it is too large Load Diff

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

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

@@ -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"]`:

View File

@@ -181,7 +181,8 @@ TEMPLATE_SUBSTITUTIONS = {
"{newline}": r"A newline: '\n'",
"{lf}": r"A line feed: '\n', alias for {newline}",
"{cr}": r"A carriage return: '\r'",
"{crlf}": r"a carriage return + line feed: '\r\n'",
"{crlf}": r"A carriage return + line feed: '\r\n'",
"{tab}": r":A tab: '\t'",
"{osxphotos_version}": f"The osxphotos version, e.g. '{__version__}'",
"{osxphotos_cmd_line}": "The full command line used to run osxphotos",
}
@@ -268,6 +269,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
@@ -307,6 +313,7 @@ PUNCTUATION = {
"lf": "\n",
"cr": "\r",
"crlf": "\r\n",
"tab": "\t",
}
@@ -329,6 +336,7 @@ class RenderOptions:
dest_path: set to the destination path of the photo (for use by {function} template), only valid with --filename
filepath: set to value for filepath of the exported photo if you want to evaluate {filepath} template
quote: quote path templates for execution in the shell
caller: which command is calling the template (e.g. 'export')
"""
none_str: str = "_"
@@ -343,6 +351,7 @@ class RenderOptions:
dest_path: Optional[str] = None
filepath: Optional[str] = None
quote: bool = False
caller: str = "export"
class PhotoTemplateParser:
@@ -665,16 +674,18 @@ class PhotoTemplate:
vals = string_test(lambda v, c: v.endswith(c))
elif operator == "==":
match = sorted(vals) == sorted(conditional_value)
if (match and not negation) or (negation and not match):
vals = ["True"]
else:
vals = []
vals = (
["True"]
if (match and not negation) or (negation and not match)
else []
)
elif operator == "!=":
match = sorted(vals) != sorted(conditional_value)
if (match and not negation) or (negation and not match):
vals = ["True"]
else:
vals = []
vals = (
["True"]
if (match and not negation) or (negation and not match)
else []
)
elif operator == "<":
vals = comparison_test(lambda v, c: v < c)
elif operator == "<=":
@@ -788,7 +799,9 @@ class PhotoTemplate:
raise ValueError(
"SyntaxError: filename and function must not be null with {function::filename.py:function_name}"
)
vals = self.get_template_value_function(subfield, field_arg)
vals = self.get_template_value_function(
subfield, field_arg, self.options.caller
)
elif field in MULTI_VALUE_SUBSTITUTIONS or field.startswith("photo"):
vals = self.get_template_value_multi(
field, subfield, path_sep=field_arg, default=default
@@ -1178,6 +1191,7 @@ class PhotoTemplate:
"remove",
"slice",
"sslice",
"filter",
] and (args is None or not len(args)):
raise SyntaxError(f"{filter_} requires arguments")
@@ -1266,12 +1280,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 +1533,17 @@ class PhotoTemplate:
def get_template_value_function(
self,
subfield,
field_arg,
subfield: str,
field_arg: Optional[str],
caller: str,
):
"""Get template value from external function"""
"""Get template value from external function
Args:
subfield: the filename and function name in for filename.py::function
field_arg: the argument to pass to the function
caller: the calling source of the template ('export' or 'import')
"""
if "::" not in subfield:
raise ValueError(
@@ -1481,8 +1562,17 @@ class PhotoTemplate:
# if no uuid, then template is being validated but not actually run
# so don't run the function
values = []
else:
elif caller == "export":
# function signature is:
# def example(photo: PhotoInfo, options: ExportOptions, args: Optional[str] = None, **kwargs) -> Union[List, str]:
values = template_func(self.photo, options=self.options, args=field_arg)
elif caller == "import":
# function signature is:
# def example(filepath: pathlib.Path, args: Optional[str] = None, **kwargs) -> Union[List, str]:
# the PhotoInfoFromFile class used by import sets `path` to the path of the file being imported
values = template_func(pathlib.Path(self.photo.path), args=field_arg)
else:
raise ValueError(f"Unhandled caller: {caller}")
if not isinstance(values, (str, list)):
raise TypeError(
@@ -1629,20 +1719,18 @@ def _get_pathlib_value(field, value, quote):
if len(parts) == 1:
return shlex.quote(value) if quote else value
if len(parts) > 2:
raise ValueError(f"Illegal value for path template: {field}")
path = parts[0]
attribute = parts[1]
path = pathlib.Path(value)
try:
val = getattr(path, attribute)
val_str = str(val)
if quote:
val_str = shlex.quote(val_str)
return val_str
except AttributeError:
raise ValueError("Illegal value for path template: {attribute}")
for attribute in parts[1:]:
try:
val = getattr(path, attribute)
path = pathlib.Path(val)
except AttributeError as e:
raise ValueError(f"Illegal value for filepath template: {attribute}") from e
val_str = str(val)
if quote:
val_str = shlex.quote(val_str)
return val_str
def format_str_value(value, format_str):
@@ -1712,3 +1800,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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

View File

@@ -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"""

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

View File

@@ -8319,3 +8319,100 @@ def test_export_no_keyword():
)
assert result.exit_code == 0
assert "Exporting 11" in result.output
def test_export_print():
"""test export --print"""
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
".",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--print",
"uuid: {uuid}",
"--uuid",
UUID_FAVORITE,
],
)
assert result.exit_code == 0
assert f"uuid: {UUID_FAVORITE}" in result.output
def test_query_print_quiet():
"""test query --print"""
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
query,
[
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--print",
"uuid: {uuid}",
"--uuid",
UUID_FAVORITE,
"--quiet",
],
)
assert result.exit_code == 0
assert result.output.strip() == f"uuid: {UUID_FAVORITE}"
def test_query_field():
"""test query --field"""
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
query,
[
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--field",
"uuid",
"{uuid}",
"--field",
"name",
"{photo.original_filename}",
"--uuid",
UUID_FAVORITE,
],
)
assert result.exit_code == 0
assert result.output.strip() == f"uuid,name\n{UUID_FAVORITE},{FILE_FAVORITE}"
def test_query_field_json():
"""test query --field --json"""
runner = CliRunner()
cwd = os.getcwd()
with runner.isolated_filesystem():
result = runner.invoke(
query,
[
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--field",
"uuid",
"{uuid}",
"--field",
"name",
"{photo.original_filename}",
"--uuid",
UUID_FAVORITE,
"--json",
],
)
assert result.exit_code == 0
json_results = json.loads(result.output)
assert json_results[0]["uuid"] == UUID_FAVORITE
assert json_results[0]["name"] == FILE_FAVORITE

125
tests/test_cli_dump.py Normal file
View File

@@ -0,0 +1,125 @@
"""Test osxphotos dump command."""
import json
import os
import os.path
import pytest
from click.testing import CliRunner
from osxphotos.cli import dump
from osxphotos.photosdb import PhotosDB
from .test_cli import CLI_PHOTOS_DB
@pytest.fixture
def photos():
"""Return photos from CLI_PHOTOS_DB"""
cwd = os.getcwd()
db_path = os.path.join(cwd, CLI_PHOTOS_DB)
return PhotosDB(db_path).photos(intrash=True)
def test_dump_basic(photos):
"""Test osxphotos dump"""
runner = CliRunner()
cwd = os.getcwd()
db_path = os.path.join(cwd, CLI_PHOTOS_DB)
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(dump, ["--db", db_path, "--deleted"])
assert result.exit_code == 0
assert result.output.startswith("uuid,filename")
for photo in photos:
assert photo.uuid in result.output
def test_dump_json(photos):
"""Test osxphotos dump --json"""
runner = CliRunner()
cwd = os.getcwd()
db_path = os.path.join(cwd, CLI_PHOTOS_DB)
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(dump, ["--db", db_path, "--deleted", "--json"])
assert result.exit_code == 0
json_data = {record["uuid"]: record for record in json.loads(result.output)}
for photo in photos:
assert photo.uuid in json_data
def test_dump_print(photos):
"""Test osxphotos dump --print"""
runner = CliRunner()
cwd = os.getcwd()
db_path = os.path.join(cwd, CLI_PHOTOS_DB)
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
dump,
[
"--db",
db_path,
"--deleted",
"--print",
"{uuid}{tab}{photo.original_filename}",
],
)
assert result.exit_code == 0
for photo in photos:
assert f"{photo.uuid}\t{photo.original_filename}" in result.output
def test_dump_field(photos):
"""Test osxphotos dump --field"""
runner = CliRunner()
cwd = os.getcwd()
db_path = os.path.join(cwd, CLI_PHOTOS_DB)
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
dump,
[
"--db",
db_path,
"--deleted",
"--field",
"uuid",
"{uuid}",
"--field",
"name",
"{photo.original_filename}",
],
)
assert result.exit_code == 0
for photo in photos:
assert f"{photo.uuid},{photo.original_filename}" in result.output
def test_dump_field_json(photos):
"""Test osxphotos dump --field --jso"""
runner = CliRunner()
cwd = os.getcwd()
db_path = os.path.join(cwd, CLI_PHOTOS_DB)
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
dump,
[
"--db",
db_path,
"--deleted",
"--field",
"uuid",
"{uuid}",
"--field",
"name",
"{photo.original_filename}",
"--json",
],
)
assert result.exit_code == 0
json_data = {record["uuid"]: record for record in json.loads(result.output)}
for photo in photos:
assert photo.uuid in json_data
assert json_data[photo.uuid]["name"] == photo.original_filename

910
tests/test_cli_import.py Normal file
View 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
View 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

View File

@@ -8,12 +8,13 @@ import osxphotos
from osxphotos.exiftool import get_exiftool_path
from osxphotos.export_db import ExportDBInMemory
from osxphotos.phototemplate import (
PUNCTUATION,
TEMPLATE_SUBSTITUTIONS,
TEMPLATE_SUBSTITUTIONS_MULTI_VALUED,
PhotoTemplate,
RenderOptions,
)
from osxphotos.photoinfo import PhotoInfoNone
from .photoinfo_mock import PhotoInfoMock
try:
@@ -238,6 +239,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 +453,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 +1245,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"
@@ -1326,3 +1346,16 @@ def test_bad_sslice(photosdb):
# bad function raises ValueError
with pytest.raises((SyntaxError, ValueError)):
rendered, _ = photo.render_template("{photo.original_filename|sslice(1:2:3:4)}")
def test_punctuation():
"""Test punctuation template fields"""
template_string = ""
expected_string = ""
for field, value in PUNCTUATION.items():
template_string += "{" + field + "}"
expected_string += f"{value}"
template = PhotoTemplate(PhotoInfoNone())
options = RenderOptions()
rendered, _ = template.render(template_string, options)
assert rendered == [expected_string]