Compare commits

..

52 Commits

Author SHA1 Message Date
Rhet Turnbull
7d923590ae Updated dependencies for pyobjc 8.0 2021-11-25 08:34:55 -08:00
Rhet Turnbull
5383ced1ca Updated CHANGELOG.md [skip ci] 2021-11-11 11:05:19 -08:00
Rhet Turnbull
0e6c92dbd9 Fix for --use-photokit with --skip-live, #537 2021-11-11 10:54:48 -08:00
Rhet Turnbull
b00978c61a Updated CHANGELOG.md [skip ci] 2021-11-07 21:35:46 -08:00
Rhet Turnbull
fb583e28e0 Updated docs [skip ci] 2021-11-07 21:31:27 -08:00
Rhet Turnbull
760386e3d7 Updated tested versions 2021-11-07 21:21:50 -08:00
Rhet Turnbull
51ba54971a Test fixes for Monterey/M1 2021-11-07 08:33:08 -08:00
Rhet Turnbull
2ffcf1e82b Updated OTL to MTL 2021-11-06 07:13:02 -07:00
Rhet Turnbull
818f4f45a4 Dependency update for Monterey 2021-10-30 07:37:23 -07:00
Rhet Turnbull
2cf19f6af1 Updated docs [skip ci] 2021-10-30 07:25:26 -07:00
Rhet Turnbull
ef82c6e32b Updated for Monterey 12.0.1 release 2021-10-28 22:05:17 -07:00
Rhet Turnbull
0e9b9d6251 Updated docs [skip ci] 2021-10-15 05:45:17 -07:00
Rhet Turnbull
419b34ea73 Fix for #526 with --update 2021-10-15 05:36:41 -07:00
Rhet Turnbull
f64c4ed374 Fixed FileUtil to use correct import 2021-10-14 21:29:45 -07:00
Rhet Turnbull
1677f404d2 Updated CHANGELOG.md [skip ci] 2021-10-11 18:07:36 -07:00
allcontributors[bot]
a612a363ed docs: add spencerc99 as a contributor for bug (#527)
* docs: update README.md [skip ci]

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

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-10-11 18:03:50 -07:00
Rhet Turnbull
202bc1144b Fix for #526 2021-10-11 17:50:07 -07:00
Rhet Turnbull
a0c654e43f Updated README.md [skip ci] 2021-10-11 17:03:45 -07:00
Rhet Turnbull
2bb677dc19 Updated docs [skip ci] 2021-10-11 16:52:22 -07:00
Rhet Turnbull
e33805fe42 Merge branch 'master' of github.com:RhetTbull/osxphotos 2021-10-11 16:00:05 -07:00
Rhet Turnbull
04ac0a1121 Fix for #524 2021-10-11 15:59:40 -07:00
Rhet Turnbull
d2b0bd4e28 Fix for #525 2021-10-11 15:59:02 -07:00
allcontributors[bot]
d754899563 docs: add oPromessa as a contributor for bug (#525)
* docs: update README.md [skip ci]

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

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2021-10-11 15:55:49 -07:00
Rhet Turnbull
4a81e643a7 Updated CHANGELOG.md [skip ci] 2021-10-11 07:35:51 -07:00
Rhet Turnbull
b23e74f8f5 Updated docs [skip ci] 2021-10-11 07:32:15 -07:00
Rhet Turnbull
5dc766249a Updated dependencies 2021-10-10 23:05:53 -07:00
Rhet Turnbull
a895833c7f Updated dependencies 2021-10-10 22:27:00 -07:00
Rhet Turnbull
3f81a3c179 Added python 3.10 to supported versions 2021-10-09 09:39:23 -07:00
Rhet Turnbull
f1235f745f Updated CHANGELOG.md [skip ci] 2021-09-30 06:13:40 -07:00
Rhet Turnbull
1ddb1de998 Updated docs [skip ci] 2021-09-30 06:11:00 -07:00
Rhet Turnbull
c472698b1d Updated REPL, now with more cowbell 2021-09-30 05:59:03 -07:00
Rhet Turnbull
4e021a0731 Updated CHANGELOG.md [skip ci] 2021-09-26 15:57:07 -07:00
Rhet Turnbull
bfbc156821 Updated docs [skip ci] 2021-09-26 15:55:03 -07:00
Rhet Turnbull
bfd6274602 Fixed AlbumInfo.owner, #239 2021-09-26 15:52:38 -07:00
Rhet Turnbull
3abaa5ae84 Updated docs [skip ci] 2021-09-26 15:43:22 -07:00
Rhet Turnbull
65115a50a9 Updated CHANGELOG.md [skip ci] 2021-09-26 15:00:34 -07:00
Rhet Turnbull
06138e15d0 version bump 2021-09-26 14:56:10 -07:00
Rhet Turnbull
14710e3178 Performance fix for #239, owner 2021-09-26 14:55:43 -07:00
Rhet Turnbull
f705f09749 Updated CHANGELOG.md [skip ci] 2021-09-26 14:54:59 -07:00
Rhet Turnbull
82c445f41e Updated CHANGELOG.md [skip ci] 2021-09-26 14:29:36 -07:00
Rhet Turnbull
1b40e9d65f Fixed tests for comment fix 2021-09-26 14:07:35 -07:00
Rhet Turnbull
725f7c8735 Updated docs [skip ci] 2021-09-26 14:00:46 -07:00
Rhet Turnbull
7cc8578148 Merge branch 'master' of github.com:RhetTbull/osxphotos 2021-09-26 13:53:32 -07:00
Rhet Turnbull
6adafb8ce7 Fixed formatting 2021-09-26 13:53:21 -07:00
Rhet Turnbull
ac47df8475 Fix for #517, #239 2021-09-26 13:51:47 -07:00
Rhet Turnbull
f680cf78ab Removed macOS-11, need to fix detected_text test 2021-09-26 09:06:17 -07:00
Rhet Turnbull
c86e84c534 Removed python 3.10-dev, not available in GH 2021-09-26 08:06:32 -07:00
Rhet Turnbull
3fb611825c Added python 3.10, macOS 11 2021-09-26 07:53:32 -07:00
Rhet Turnbull
1cfdad0176 Updated CHANGELOG.md [skip ci] 2021-09-25 22:51:30 -07:00
Rhet Turnbull
59ba325273 Updated docs [skip ci] 2021-09-25 22:38:46 -07:00
Rhet Turnbull
c4b7c2623f Implemented PhotoInfo.owner, AlbumInfo.owner, #216, #239 2021-09-25 22:33:37 -07:00
Rhet Turnbull
e5b2d2ee45 Updated CHANGELOG.md [skip ci] 2021-09-25 09:51:50 -07:00
58 changed files with 989 additions and 347 deletions

View File

@@ -250,6 +250,24 @@
"contributions": [
"bug"
]
},
{
"login": "oPromessa",
"name": "oPromessa",
"avatar_url": "https://avatars.githubusercontent.com/u/21261491?v=4",
"profile": "https://github.com/oPromessa",
"contributions": [
"bug"
]
},
{
"login": "spencerc99",
"name": "Spencer Chang",
"avatar_url": "https://avatars.githubusercontent.com/u/14796580?v=4",
"profile": "http://spencerchang.me",
"contributions": [
"bug"
]
}
],
"contributorsPerLine": 7,

View File

@@ -4,6 +4,115 @@ 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.43.4](https://github.com/RhetTbull/osxphotos/compare/v0.43.3...v0.43.4)
> 11 November 2021
- Fix for --use-photokit with --skip-live, #537 [`0e6c92d`](https://github.com/RhetTbull/osxphotos/commit/0e6c92dbd951dd0e63cfb8b6d64e6ab96ece5955)
#### [v0.43.3](https://github.com/RhetTbull/osxphotos/compare/v0.43.1...v0.43.3)
> 7 November 2021
- Updated docs [skip ci] [`fb583e2`](https://github.com/RhetTbull/osxphotos/commit/fb583e28e0fc2c23bf24052db8a5ee669d8c92f5)
- Updated OTL to MTL [`2ffcf1e`](https://github.com/RhetTbull/osxphotos/commit/2ffcf1e82bfc013a4a9e0e7a709a7c1395c074ce)
- Test fixes for Monterey/M1 [`51ba549`](https://github.com/RhetTbull/osxphotos/commit/51ba54971a874cfce00368aa5be5380b3439c254)
#### [v0.43.1](https://github.com/RhetTbull/osxphotos/compare/v0.43.0...v0.43.1)
> 30 October 2021
- Dependency update for Monterey [`818f4f4`](https://github.com/RhetTbull/osxphotos/commit/818f4f45a4ce520b0ba1c688eabd2f4311be9540)
- Updated docs [skip ci] [`2cf19f6`](https://github.com/RhetTbull/osxphotos/commit/2cf19f6af1a03767e4d53eee556c4d3ed9af1776)
#### [v0.43.0](https://github.com/RhetTbull/osxphotos/compare/v0.42.94...v0.43.0)
> 28 October 2021
- Updated for Monterey 12.0.1 release [`ef82c6e`](https://github.com/RhetTbull/osxphotos/commit/ef82c6e32b536b0677530133892f95b852c6dce0)
#### [v0.42.94](https://github.com/RhetTbull/osxphotos/compare/v0.42.93...v0.42.94)
> 15 October 2021
- docs: add spencerc99 as a contributor for bug [`#527`](https://github.com/RhetTbull/osxphotos/pull/527)
- Fix for #526 with --update [`419b34e`](https://github.com/RhetTbull/osxphotos/commit/419b34ea73f15ccbe29f51896e11e9735ea5786b)
- Updated docs [skip ci] [`0e9b9d6`](https://github.com/RhetTbull/osxphotos/commit/0e9b9d625190b94c1dd68276e3b0e5367002d87c)
- Fixed FileUtil to use correct import [`f64c4ed`](https://github.com/RhetTbull/osxphotos/commit/f64c4ed374c120a95fe8adea26bd44852ca67e31)
#### [v0.42.93](https://github.com/RhetTbull/osxphotos/compare/v0.42.92...v0.42.93)
> 11 October 2021
- Fix for #526 [`202bc11`](https://github.com/RhetTbull/osxphotos/commit/202bc1144bc842ddec825eef0745830d56170aba)
- Updated README.md [skip ci] [`a0c654e`](https://github.com/RhetTbull/osxphotos/commit/a0c654e43f4aa5389a96c3c84fd7037c33d23404)
#### [v0.42.92](https://github.com/RhetTbull/osxphotos/compare/v0.42.91...v0.42.92)
> 11 October 2021
- docs: add oPromessa as a contributor for bug [`#525`](https://github.com/RhetTbull/osxphotos/pull/525)
- Fix for #524 [`04ac0a1`](https://github.com/RhetTbull/osxphotos/commit/04ac0a11215b275178013e60c6a61b9f1b3603c9)
- Fix for #525 [`d2b0bd4`](https://github.com/RhetTbull/osxphotos/commit/d2b0bd4e28cfdf3c930aa6ae3317549327b0e29c)
- Updated docs [skip ci] [`2bb677d`](https://github.com/RhetTbull/osxphotos/commit/2bb677dc19abaf254bc66e2cd788676e0613e548)
#### [v0.42.91](https://github.com/RhetTbull/osxphotos/compare/v0.42.90...v0.42.91)
> 11 October 2021
- Updated docs [skip ci] [`b23e74f`](https://github.com/RhetTbull/osxphotos/commit/b23e74f8f5a8387564108c330c3f8ac11189860d)
- Added python 3.10 to supported versions [`3f81a3c`](https://github.com/RhetTbull/osxphotos/commit/3f81a3c179dde37e9811ef19c847920bb3bd514c)
- Updated dependencies [`a895833`](https://github.com/RhetTbull/osxphotos/commit/a895833c7f0a264488e671f1735f9e10d2618e2d)
#### [v0.42.90](https://github.com/RhetTbull/osxphotos/compare/v0.42.89...v0.42.90)
> 30 September 2021
- Updated REPL, now with more cowbell [`c472698`](https://github.com/RhetTbull/osxphotos/commit/c472698b1d0d8ff9f4d1bde715859bf766f99290)
- Updated docs [skip ci] [`1ddb1de`](https://github.com/RhetTbull/osxphotos/commit/1ddb1de99841e65b690ffc1cbcc5e42e6e25f727)
#### [v0.42.89](https://github.com/RhetTbull/osxphotos/compare/v0.42.88...v0.42.89)
> 26 September 2021
- Updated docs [skip ci] [`bfbc156`](https://github.com/RhetTbull/osxphotos/commit/bfbc156821d2d262b7bd9c4437e23e310da10769)
- Updated docs [skip ci] [`3abaa5a`](https://github.com/RhetTbull/osxphotos/commit/3abaa5ae84ca44cd900f1e3af4532ab405d41a09)
- Fixed AlbumInfo.owner, #239 [`bfd6274`](https://github.com/RhetTbull/osxphotos/commit/bfd627460255c65f870bca6d036401e8792d29d5)
#### [v0.42.88](https://github.com/RhetTbull/osxphotos/compare/v0.42.87...v0.42.88)
> 26 September 2021
- Performance fix for #239, owner [`14710e3`](https://github.com/RhetTbull/osxphotos/commit/14710e31789d71b2c948a37722fb6054aca4d85e)
- version bump [`06138e1`](https://github.com/RhetTbull/osxphotos/commit/06138e15d0b87e4865a9ef0cc542303edb44c861)
#### [v0.42.87](https://github.com/RhetTbull/osxphotos/compare/v0.42.86...v0.42.87)
> 26 September 2021
#### [v0.42.86](https://github.com/RhetTbull/osxphotos/compare/v0.42.85...v0.42.86)
> 26 September 2021
- Fix for #517, #239 [`ac47df8`](https://github.com/RhetTbull/osxphotos/commit/ac47df8475762fe8c8f63ad5ffa83b1e20d116b8)
- Fixed formatting [`6adafb8`](https://github.com/RhetTbull/osxphotos/commit/6adafb8ce70e95a9f0bec1a3db6362742fcd1b0d)
- Updated docs [skip ci] [`725f7c8`](https://github.com/RhetTbull/osxphotos/commit/725f7c87351353efeee8c43c3c7f8a95acb14490)
#### [v0.42.85](https://github.com/RhetTbull/osxphotos/compare/v0.42.84...v0.42.85)
> 25 September 2021
- Implemented PhotoInfo.owner, AlbumInfo.owner, #216, #239 [`c4b7c26`](https://github.com/RhetTbull/osxphotos/commit/c4b7c2623f077d9964d5d578ce6c01bb83fab088)
- Updated docs [skip ci] [`59ba325`](https://github.com/RhetTbull/osxphotos/commit/59ba325273b2f16935be944fd46c1237ce637bb8)
#### [v0.42.84](https://github.com/RhetTbull/osxphotos/compare/v0.42.83...v0.42.84)
> 25 September 2021
- Fix for #516 [`e3e1da2`](https://github.com/RhetTbull/osxphotos/commit/e3e1da2fd898896595fc851288f905bd4e2150f8)
- Updated docs [skip ci] [`64c226b`](https://github.com/RhetTbull/osxphotos/commit/64c226b85529581e393a2d0604b41c37a8dc2eaf)
- Update docs [`c429a86`](https://github.com/RhetTbull/osxphotos/commit/c429a860b1ebeb77f3c3e36e9660fc9153d85d11)
#### [v0.42.83](https://github.com/RhetTbull/osxphotos/compare/v0.42.82...v0.42.83)
> 15 September 2021

View File

@@ -2,4 +2,5 @@ include README.md
include README.rst
include osxphotos/templates/*
include osxphotos/phototemplate.tx
include osxphotos/phototemplate.md
include osxphotos/phototemplate.md
include osxphotos/queries/*

View File

@@ -4,7 +4,9 @@
[![tests](https://github.com/RhetTbull/osxphotos/workflows/Tests/badge.svg)](https://github.com/RhetTbull/osxphotos/workflows/Tests/badge.svg)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/osxphotos)
[![Downloads](https://static.pepy.tech/personalized-badge/osxphotos?period=month&units=international_system&left_color=black&right_color=brightgreen&left_text=downloads/month)](https://pepy.tech/project/osxphotos)
[![All Contributors](https://img.shields.io/badge/all_contributors-26-orange.svg?style=flat)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-28-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.
@@ -1258,8 +1260,8 @@ s
** Templating System **
The templating system converts one or template statements, written in osxphotos
templating language, to one or more rendered values using information from the
photo being processed.
metadata templating language, to one or more rendered values using information
from the photo being processed.
In its simplest form, a template statement has the form: "{template_field}", for
example "{title}" which would resolve to the title of the photo.
@@ -1702,7 +1704,7 @@ Substitution Description
{lf} A line feed: '\n', alias for {newline}
{cr} A carriage return: '\r'
{crlf} a carriage return + line feed: '\r\n'
{osxphotos_version} The osxphotos version, e.g. '0.42.84'
{osxphotos_version} The osxphotos version, e.g. '0.43.4'
{osxphotos_cmd_line} The full command line used to run osxphotos
The following substitutions may result in multiple values. Thus if specified for
@@ -2373,6 +2375,8 @@ For example, in my library, Photos says I have 19,386 photos and 474 movies. Ho
#### <a name="getphoto">`get_photo(uuid)`</A>
Returns a single PhotoInfo instance for photo with UUID matching `uuid` or None if no photo is found matching `uuid`. If you know the UUID of a photo, `get_photo()` is much faster than `photos`. See also [photos()](#photos).
#### `execute(sql)`
Execute sql statement against the Photos database and return a sqlite cursor with the results.
### PhotoInfo
PhotosDB.photos() returns a list of PhotoInfo objects. Each PhotoInfo object represents a single photo in the Photos library.
@@ -2516,7 +2520,12 @@ Returns a [PlaceInfo](#PlaceInfo) object with reverse geolocation data or None i
#### `shared`
Returns True if photo is in a shared album, otherwise False.
**Note**: *Only valid on Photos 5 / MacOS 10.15+; on Photos <= 4, returns None instead of True/False.
**Note**: *Only valid on Photos 5 / MacOS 10.15+; on Photos <= 4, returns None.
#### `owner`
Returns full name of the photo owner (person who shared the photo) for shared photos or None if photo is not shared. Also returns None if you are the person who shared the photo.
**Note**: *Only valid on Photos 5 / MacOS 10.15+; on Photos <= 4, returns None.
#### `comments`
Returns list of [CommentInfo](#commentinfo) objects for comments on shared photos or empty list if no comments.
@@ -2766,7 +2775,7 @@ If overwrite=False and increment=False, export will fail if destination file alr
Render template string for photo. none_str is used if template substitution results in None value and no default specified.
- `template_str`: str in osxphotos template language (OTL) format. See also [Template System](#template-system) table. See notes below regarding specific details of the syntax.
- `template_str`: str in metadata template language (MTL) format. See also [Template System](#template-system) table. See notes below regarding specific details of the syntax.
- `options`: an optional osxphotos.phototemplate.RenderOptions object specifying the options to pass to the rendering engine.
`RenderOptions` has the following properties:
@@ -2890,6 +2899,11 @@ Photos Library
#### `parent`
Returns a [FolderInfo](#FolderInfo) object representing the albums parent folder or `None` if album is not a in a folder.
#### `owner`
Returns full name of the album owner (person who shared the album) for shared albums or None if album is not shared.
**Note**: *Only valid on Photos 5 / MacOS 10.15+; on Photos <= 4, returns None.
### ImportInfo
PhotosDB.import_info returns a list of ImportInfo objects. Each ImportInfo object represents an import session in the library. PhotoInfo.import_info returns a single ImportInfo object representing the import session for the photo (or `None` if no associated import session).
@@ -3257,7 +3271,6 @@ The following additional properties are also available but are not yet fully doc
- `manual`:
- `face_type`:
- `age_type`:
- `bald_type`:
- `eye_makeup_type`:
- `eye_state`:
- `facial_hair_type`:
@@ -3340,7 +3353,7 @@ To get the path of every raw photo, whether it's a single raw photo or a raw+JPE
### Template System
<!-- OSXPHOTOS-TEMPLATE-HELP:START - Do not remove or modify this section -->
The templating system converts one or template statements, written in osxphotos templating language, to one or more rendered values using information from the photo being processed.
The templating system converts one or template statements, written in osxphotos metadata templating language, to one or more rendered values using information from the photo being processed.
In its simplest form, a template statement has the form: `"{template_field}"`, for example `"{title}"` which would resolve to the title of the photo.
@@ -3561,7 +3574,7 @@ The following template field substitutions are availabe for use the templating s
|{lf}|A line feed: '\n', alias for {newline}|
|{cr}|A carriage return: '\r'|
|{crlf}|a carriage return + line feed: '\r\n'|
|{osxphotos_version}|The osxphotos version, e.g. '0.42.84'|
|{osxphotos_version}|The osxphotos version, e.g. '0.43.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|
@@ -3785,6 +3798,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/mkirkland4874"><img src="https://avatars.githubusercontent.com/u/36466711?v=4?s=75" width="75px;" alt=""/><br /><sub><b>mkirkland4874</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Amkirkland4874" title="Bug reports">🐛</a> <a href="#example-mkirkland4874" title="Examples">💡</a></td>
<td align="center"><a href="https://github.com/jcommisso07"><img src="https://avatars.githubusercontent.com/u/3111054?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Joseph Commisso</b></sub></a><br /><a href="#data-jcommisso07" title="Data">🔣</a></td>
<td align="center"><a href="https://github.com/dssinger"><img src="https://avatars.githubusercontent.com/u/1817903?v=4?s=75" width="75px;" alt=""/><br /><sub><b>David Singer</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Adssinger" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/oPromessa"><img src="https://avatars.githubusercontent.com/u/21261491?v=4?s=75" width="75px;" alt=""/><br /><sub><b>oPromessa</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3AoPromessa" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://spencerchang.me"><img src="https://avatars.githubusercontent.com/u/14796580?v=4?s=75" width="75px;" alt=""/><br /><sub><b>Spencer Chang</b></sub></a><br /><a href="https://github.com/RhetTbull/osxphotos/issues?q=author%3Aspencerc99" title="Bug reports">🐛</a></td>
</tr>
</table>
@@ -3826,6 +3841,8 @@ For additional details about how osxphotos is implemented or if you would like t
- [textx](https://github.com/textX/textX)
- [bitmath](https://github.com/tbielawa/bitmath)
- [more-itertools](https://github.com/more-itertools/more-itertools)
- [ptpython](https://github.com/prompt-toolkit/ptpython)
- [objexplore](https://github.com/kylepollina/objexplore)
## Acknowledgements

View File

@@ -7,5 +7,6 @@
rm -rf dist; rm -rf build
python3 utils/update_readme.py
(cd docsrc && make github && make pdf)
python3 setup.py sdist bdist_wheel
# python3 setup.py sdist bdist_wheel
python3 -m build
./make_cli_exe.sh

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: c8c78e92de35f8ff4cf0b9d6bd35d796
config: 7a3415c9b6b46da1269550f16ddeb35c
tags: 645f666f9bcd5a90fca523b33c5a78b7

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Overview: module code &#8212; osxphotos 0.42.84 documentation</title>
<title>Overview: module code &#8212; osxphotos 0.43.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../_static/alabaster.css" />
<script data-url_root="../" id="documentation_options" src="../_static/documentation_options.js"></script>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos.photoinfo._photoinfo_export &#8212; osxphotos 0.42.84 documentation</title>
<title>osxphotos.photoinfo._photoinfo_export &#8212; osxphotos 0.43.4 documentation</title>
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../../_static/alabaster.css" />
<script data-url_root="../../../" id="documentation_options" src="../../../_static/documentation_options.js"></script>
@@ -945,7 +945,14 @@
<span class="n">preview_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="bp">self</span><span class="o">.</span><span class="n">path_derivatives</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">preview_ext</span> <span class="o">=</span> <span class="n">preview_path</span><span class="o">.</span><span class="n">suffix</span>
<span class="n">preview_name</span> <span class="o">=</span> <span class="n">dest</span><span class="o">.</span><span class="n">parent</span> <span class="o">/</span> <span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">dest</span><span class="o">.</span><span class="n">stem</span><span class="si">}{</span><span class="n">preview_suffix</span><span class="si">}{</span><span class="n">preview_ext</span><span class="si">}</span><span class="s2">&quot;</span>
<span class="n">preview_name</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">increment_filename</span><span class="p">(</span><span class="n">preview_name</span><span class="p">))</span>
<span class="c1"># if original is missing, the filename won&#39;t have been incremented so</span>
<span class="c1"># need to check here to make sure there aren&#39;t duplicate preview files in</span>
<span class="c1"># the export directory</span>
<span class="n">preview_name</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">preview_name</span>
<span class="k">if</span> <span class="n">overwrite</span> <span class="ow">or</span> <span class="n">update</span>
<span class="k">else</span> <span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="n">increment_filename</span><span class="p">(</span><span class="n">preview_name</span><span class="p">))</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">preview_path</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">results</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_export_photo</span><span class="p">(</span>
<span class="n">preview_path</span><span class="p">,</span>
@@ -1292,6 +1299,7 @@
<span class="n">dest</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
<span class="n">version</span><span class="o">=</span><span class="n">PHOTOS_VERSION_CURRENT</span><span class="p">,</span>
<span class="n">overwrite</span><span class="o">=</span><span class="n">overwrite</span><span class="p">,</span>
<span class="n">video</span><span class="o">=</span><span class="n">live_photo</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">all_results</span><span class="o">.</span><span class="n">exported</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">exported</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>
@@ -1339,6 +1347,7 @@
<span class="n">dest</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
<span class="n">version</span><span class="o">=</span><span class="n">PHOTOS_VERSION_ORIGINAL</span><span class="p">,</span>
<span class="n">overwrite</span><span class="o">=</span><span class="n">overwrite</span><span class="p">,</span>
<span class="n">video</span><span class="o">=</span><span class="n">live_photo</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">all_results</span><span class="o">.</span><span class="n">exported</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">exported</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>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos.photoinfo.photoinfo &#8212; osxphotos 0.42.83 documentation</title>
<title>osxphotos.photoinfo.photoinfo &#8212; osxphotos 0.42.87 documentation</title>
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../../_static/alabaster.css" />
<script data-url_root="../../../" id="documentation_options" src="../../../_static/documentation_options.js"></script>
@@ -71,6 +71,7 @@
<span class="kn">from</span> <span class="nn">..personinfo</span> <span class="kn">import</span> <span class="n">FaceInfo</span><span class="p">,</span> <span class="n">PersonInfo</span>
<span class="kn">from</span> <span class="nn">..phototemplate</span> <span class="kn">import</span> <span class="n">PhotoTemplate</span><span class="p">,</span> <span class="n">RenderOptions</span>
<span class="kn">from</span> <span class="nn">..placeinfo</span> <span class="kn">import</span> <span class="n">PlaceInfo4</span><span class="p">,</span> <span class="n">PlaceInfo5</span>
<span class="kn">from</span> <span class="nn">..query_builder</span> <span class="kn">import</span> <span class="n">get_query</span>
<span class="kn">from</span> <span class="nn">..text_detection</span> <span class="kn">import</span> <span class="n">detect_text</span>
<span class="kn">from</span> <span class="nn">..uti</span> <span class="kn">import</span> <span class="n">get_preferred_uti_extension</span><span class="p">,</span> <span class="n">get_uti_for_extension</span>
<span class="kn">from</span> <span class="nn">..utils</span> <span class="kn">import</span> <span class="n">_debug</span><span class="p">,</span> <span class="n">_get_resource_loc</span><span class="p">,</span> <span class="n">findfiles</span>
@@ -1131,6 +1132,26 @@
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="sa">f</span><span class="s2">&quot;Did not find signature for </span><span class="si">{</span><span class="bp">self</span><span class="o">.</span><span class="n">uuid</span><span class="si">}</span><span class="s2"> in _db_signatures&quot;</span><span class="p">)</span>
<span class="k">return</span> <span class="n">duplicates</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">owner</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Return name of photo owner for shared photos (Photos 5+ only), or None if not shared&quot;&quot;&quot;</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_version</span> <span class="o">&lt;=</span> <span class="n">_PHOTOS_4_VERSION</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">None</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_owner</span>
<span class="k">except</span> <span class="ne">AttributeError</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">personid</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_info</span><span class="p">[</span><span class="s2">&quot;cloudownerhashedpersonid&quot;</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_owner</span> <span class="o">=</span> <span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_db</span><span class="o">.</span><span class="n">_db_hashed_person_id</span><span class="p">[</span><span class="n">personid</span><span class="p">][</span><span class="s2">&quot;full_name&quot;</span><span class="p">]</span>
<span class="k">if</span> <span class="n">personid</span>
<span class="k">else</span> <span class="kc">None</span>
<span class="p">)</span>
<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_owner</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_owner</span>
<div class="viewcode-block" id="PhotoInfo.render_template"><a class="viewcode-back" href="../../../reference.html#osxphotos.PhotoInfo.render_template">[docs]</a> <span class="k">def</span> <span class="nf">render_template</span><span class="p">(</span>
<span class="bp">self</span><span class="p">,</span> <span class="n">template_str</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">options</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">RenderOptions</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="p">):</span>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos.photosdb.photosdb &#8212; osxphotos 0.42.80 documentation</title>
<title>osxphotos.photosdb.photosdb &#8212; osxphotos 0.42.92 documentation</title>
<link rel="stylesheet" type="text/css" href="../../../_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="../../../_static/alabaster.css" />
<script data-url_root="../../../" id="documentation_options" src="../../../_static/documentation_options.js"></script>
@@ -363,6 +363,8 @@
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_process_database5</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_db_connection</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_db_connection</span><span class="p">()</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">keywords_as_dict</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;return keywords as dict of keyword, count in reverse sorted order (descending)&quot;&quot;&quot;</span>
@@ -823,8 +825,8 @@
<span class="s2">&quot;creation_date&quot;</span><span class="p">:</span> <span class="n">album</span><span class="p">[</span><span class="mi">8</span><span class="p">],</span>
<span class="s2">&quot;start_date&quot;</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
<span class="s2">&quot;end_date&quot;</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
<span class="s2">&quot;customsortascending&quot;</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
<span class="s2">&quot;customsortkey&quot;</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
<span class="s2">&quot;customsortascending&quot;</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
<span class="s2">&quot;customsortkey&quot;</span><span class="p">:</span> <span class="kc">None</span><span class="p">,</span> <span class="c1"># Photos 5 only</span>
<span class="p">}</span>
<span class="c1"># get details about folders</span>
@@ -1137,7 +1139,9 @@
<span class="c1"># get info on special types</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;specialType&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;masterModelID&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">26</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;pk&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">26</span><span class="p">]</span> <span class="c1"># same as masterModelID, to match Photos 5</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;pk&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span>
<span class="mi">26</span>
<span class="p">]</span> <span class="c1"># same as masterModelID, to match Photos 5</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;panorama&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">if</span> <span class="n">row</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">==</span> <span class="mi">1</span> <span class="k">else</span> <span class="kc">False</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;slow_mo&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">if</span> <span class="n">row</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">==</span> <span class="mi">2</span> <span class="k">else</span> <span class="kc">False</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;time_lapse&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">if</span> <span class="n">row</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">==</span> <span class="mi">3</span> <span class="k">else</span> <span class="kc">False</span>
@@ -1228,6 +1232,9 @@
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;import_uuid&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">44</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;fok_import_session&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># photos 5+ only, for shared photos</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">[</span><span class="n">uuid</span><span class="p">][</span><span class="s2">&quot;cloudownerhashedpersonid&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># compute signatures for finding possible duplicates</span>
<span class="n">signature</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_duplicate_signature</span><span class="p">(</span><span class="n">uuid</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
@@ -1956,7 +1963,8 @@
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZTRASHEDDATE,</span>
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZSAVEDASSETTYPE,</span>
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZADDEDDATE,</span>
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.Z_PK</span>
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.Z_PK,</span>
<span class="s2"> </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZCLOUDOWNERHASHEDPERSONID</span>
<span class="s2"> FROM </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2"> </span>
<span class="s2"> JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.Z_PK </span>
<span class="s2"> ORDER BY </span><span class="si">{</span><span class="n">asset_table</span><span class="si">}</span><span class="s2">.ZUUID &quot;&quot;&quot;</span>
@@ -2006,6 +2014,7 @@
<span class="c1"># 40 ZGENERICASSET.ZSAVEDASSETTYPE -- how item imported</span>
<span class="c1"># 41 ZGENERICASSET.ZADDEDDATE -- date item added to the library</span>
<span class="c1"># 42 ZGENERICASSET.Z_PK -- primary key</span>
<span class="c1"># 43 ZGENERICASSET.ZCLOUDOWNERHASHEDPERSONID -- used to look up owner name (for shared photos)</span>
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">c</span><span class="p">:</span>
<span class="n">uuid</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
@@ -2191,6 +2200,7 @@
<span class="n">info</span><span class="p">[</span><span class="s2">&quot;added_date&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">(</span><span class="mi">1970</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">info</span><span class="p">[</span><span class="s2">&quot;pk&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">42</span><span class="p">]</span>
<span class="n">info</span><span class="p">[</span><span class="s2">&quot;cloudownerhashedpersonid&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">row</span><span class="p">[</span><span class="mi">43</span><span class="p">]</span>
<span class="c1"># initialize import session info which will be filled in later</span>
<span class="c1"># not every photo has an import session so initialize all records now</span>
@@ -3319,9 +3329,9 @@
<span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">regex</span><span class="p">:</span>
<span class="n">flags</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">IGNORECASE</span> <span class="k">if</span> <span class="n">options</span><span class="o">.</span><span class="n">ignore_case</span> <span class="k">else</span> <span class="mi">0</span>
<span class="n">render_options</span> <span class="o">=</span> <span class="n">RenderOptions</span><span class="p">(</span><span class="n">none_str</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">photo_list</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">regex</span><span class="p">,</span> <span class="n">template</span> <span class="ow">in</span> <span class="n">options</span><span class="o">.</span><span class="n">regex</span><span class="p">:</span>
<span class="n">regex</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">regex</span><span class="p">,</span> <span class="n">flags</span><span class="p">)</span>
<span class="n">photo_list</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">photos</span><span class="p">:</span>
<span class="n">rendered</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">p</span><span class="o">.</span><span class="n">render_template</span><span class="p">(</span><span class="n">template</span><span class="p">,</span> <span class="n">render_options</span><span class="p">)</span>
<span class="k">for</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">rendered</span><span class="p">:</span>
@@ -3387,6 +3397,10 @@
<span class="k">return</span> <span class="n">photos</span></div>
<div class="viewcode-block" id="PhotosDB.execute"><a class="viewcode-back" href="../../../reference.html#osxphotos.PhotosDB.execute">[docs]</a> <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">sql</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Execute sql statement and return cursor&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_db_connection</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="n">sql</span><span class="p">)</span></div>
<span class="k">def</span> <span class="nf">_duplicate_signature</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">uuid</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Compute a signature for finding possible duplicates&quot;&quot;&quot;</span>
<span class="k">return</span> <span class="p">(</span>
@@ -3412,7 +3426,11 @@
<span class="sd">&quot;&quot;&quot;Returns number of photos in the database</span>
<span class="sd"> Includes recently deleted photos and non-selected burst images</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">)</span></div>
<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_dbphotos</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__del__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">if</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="s2">&quot;_db_connection&quot;</span><span class="p">,</span> <span class="kc">None</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_db_connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span></div>
<span class="k">def</span> <span class="nf">_get_photos_by_attribute</span><span class="p">(</span><span class="n">photos</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">values</span><span class="p">,</span> <span class="n">ignore_case</span><span class="p">):</span>

View File

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

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos command line interface (CLI) &#8212; osxphotos 0.42.84 documentation</title>
<title>osxphotos command line interface (CLI) &#8212; osxphotos 0.43.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -1561,7 +1561,7 @@ if more than one option is provided, they are treated as “AND”
</div>
<div class="section" id="osxphotos-repl">
<h3>repl<a class="headerlink" href="#osxphotos-repl" title="Permalink to this headline"></a></h3>
<p>Run interactive osxphotos shell</p>
<p>Run interactive osxphotos REPL shell (useful for debugging, prototyping, and inspecting your Photos library)</p>
<div class="highlight-shell notranslate"><div class="highlight"><pre><span></span>osxphotos repl <span class="o">[</span>OPTIONS<span class="o">]</span>
</pre></div>
</div>
@@ -1572,6 +1572,12 @@ if more than one option is provided, they are treated as “AND”
<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-repl-emacs">
<span class="sig-name descname"><span class="pre">--emacs</span></span><span class="sig-prename descclassname"></span><a class="headerlink" href="#cmdoption-osxphotos-repl-emacs" title="Permalink to this definition"></a></dt>
<dd><p>Launch REPL with Emacs keybindings (default is vi bindings)</p>
</dd></dl>
</div>
<div class="section" id="osxphotos-tutorial">
<h3>tutorial<a class="headerlink" href="#osxphotos-tutorial" title="Permalink to this headline"></a></h3>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &#8212; osxphotos 0.42.84 documentation</title>
<title>Index &#8212; osxphotos 0.43.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -254,6 +254,13 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-export-edited-suffix">osxphotos-export command line option</a>
</li>
</ul></li>
<li>
--emacs
<ul>
<li><a href="cli.html#cmdoption-osxphotos-repl-emacs">osxphotos-repl command line option</a>
</li>
</ul></li>
<li>
@@ -1333,14 +1340,16 @@
<h2 id="E">E</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.PhotosDB.execute">execute() (osxphotos.PhotosDB method)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoInfo.exif_info">exif_info (osxphotos.PhotoInfo property)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoInfo.exiftool">exiftool (osxphotos.PhotoInfo property)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoInfo.export">export() (osxphotos.PhotoInfo method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#osxphotos.PhotoInfo.export">export() (osxphotos.PhotoInfo method)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoInfo.export2">export2() (osxphotos.PhotoInfo method)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoInfo.ExifInfo.exposure_bias">exposure_bias (osxphotos.PhotoInfo.ExifInfo attribute)</a>
@@ -2102,6 +2111,8 @@
<ul>
<li><a href="cli.html#cmdoption-osxphotos-repl-db">--db &lt;Photos database path&gt;</a>
</li>
<li><a href="cli.html#cmdoption-osxphotos-repl-emacs">--emacs</a>
</li>
</ul></li>
<li>
@@ -2112,6 +2123,8 @@
</li>
</ul></li>
<li><a href="reference.html#osxphotos.PhotoInfo.ScoreInfo.overall">overall (osxphotos.PhotoInfo.ScoreInfo attribute)</a>
</li>
<li><a href="reference.html#osxphotos.PhotoInfo.owner">owner (osxphotos.PhotoInfo property)</a>
</li>
</ul></td>
</tr></table>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to osxphotoss documentation! &#8212; osxphotos 0.42.84 documentation</title>
<title>Welcome to osxphotoss documentation! &#8212; osxphotos 0.43.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos &#8212; osxphotos 0.42.84 documentation</title>
<title>osxphotos &#8212; osxphotos 0.43.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>

Binary file not shown.

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>osxphotos package &#8212; osxphotos 0.42.84 documentation</title>
<title>osxphotos package &#8212; osxphotos 0.43.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
@@ -90,6 +90,12 @@ valid only on Photos 5; on Photos &lt;= 4, prints warning and returns empty dict
<dd><p>return the database version as stored in LiGlobals table</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="osxphotos.PhotosDB.execute">
<span class="sig-name descname"><span class="pre">execute</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">sql</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/osxphotos/photosdb/photosdb.html#PhotosDB.execute"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#osxphotos.PhotosDB.execute" title="Permalink to this definition"></a></dt>
<dd><p>Execute sql statement and return cursor</p>
</dd></dl>
<dl class="py property">
<dt class="sig sig-object py" id="osxphotos.PhotosDB.folder_info">
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">folder_info</span></span><a class="headerlink" href="#osxphotos.PhotosDB.folder_info" title="Permalink to this definition"></a></dt>
@@ -1143,6 +1149,12 @@ Photos 5 mangles filenames upon import</p>
<dd><p>returns width of the original photo version in pixels</p>
</dd></dl>
<dl class="py property">
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.owner">
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">owner</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.owner" title="Permalink to this definition"></a></dt>
<dd><p>Return name of photo owner for shared photos (Photos 5+ only), or None if not shared</p>
</dd></dl>
<dl class="py property">
<dt class="sig sig-object py" id="osxphotos.PhotoInfo.panorama">
<em class="property"><span class="pre">property</span> </em><span class="sig-name descname"><span class="pre">panorama</span></span><a class="headerlink" href="#osxphotos.PhotoInfo.panorama" title="Permalink to this definition"></a></dt>

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &#8212; osxphotos 0.42.84 documentation</title>
<title>Search &#8212; osxphotos 0.43.4 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/alabaster.css" />

File diff suppressed because one or more lines are too long

View File

@@ -95,6 +95,7 @@ _TESTED_OS_VERSIONS = [
("11", "3"),
("11", "4"),
("11", "5"),
("11", "6"),
]
# Photos 5 has persons who are empty string if unidentified face
@@ -276,12 +277,15 @@ POST_COMMAND_CATEGORIES = {
# "deleted_directories": "When used with '--cleanup', all directories deleted during the export",
}
class AlbumSortOrder(Enum):
"""Album Sort Order"""
UNKNOWN = 0
MANUAL = 1
NEWEST_FIRST = 2
OLDEST_FIRST = 3
TITLE = 5
TEXT_DETECTION_CONFIDENCE_THRESHOLD = 0.75
TEXT_DETECTION_CONFIDENCE_THRESHOLD = 0.75

View File

@@ -1,3 +1,3 @@
""" version info """
__version__ = "0.42.84"
__version__ = "0.43.5"

View File

@@ -22,6 +22,7 @@ from ._constants import (
AlbumSortOrder,
)
from .datetime_utils import get_local_tz
from .query_builder import get_query
def sort_list_by_keys(values, sort_keys):
@@ -131,6 +132,28 @@ class AlbumInfoBaseClass:
def photos(self):
return []
@property
def owner(self):
"""Return name of photo owner for shared album (Photos 5+ only), or None if not shared"""
if self._db._db_version <= _PHOTOS_4_VERSION:
return None
try:
return self._owner
except AttributeError:
try:
personid = self._db._dbalbum_details[self.uuid][
"cloudownerhashedpersonid"
]
self._owner = (
self._db._db_hashed_person_id[personid]["full_name"]
if personid
else None
)
except KeyError:
self._owner = None
return self._owner
def __len__(self):
"""return number of photos contained in album"""
return len(self.photos)

View File

@@ -55,10 +55,11 @@ from .exiftool import get_exiftool_path
from .export_db import ExportDB, ExportDBInMemory
from .fileutil import FileUtil, FileUtilNoOp
from .path_utils import is_valid_filepath, sanitize_filename, sanitize_filepath
from .photoinfo import ExportResults
from .photoinfo import ExportResults, PhotoInfo
from .photokit import check_photokit_authorization, request_photokit_authorization
from .photosalbum import PhotosAlbum
from .phototemplate import PhotoTemplate, RenderOptions
from .pyrepl import embed_repl
from .queryoptions import QueryOptions
from .uti import get_preferred_uti_extension
from .utils import expand_and_validate_filepath, load_function
@@ -4011,6 +4012,32 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
osxphotos uses the following 3rd party software licensed under the BSD-3-Clause License:
Click (Copyright 2014 Pallets), ptpython (Copyright (c) 2015, Jonathan Slenders)
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list
of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be
used to endorse or promote products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
click.echo(f"osxphotos, version {__version__}")
click.echo("")
@@ -4032,7 +4059,7 @@ def tutorial(ctx, cli_obj, width):
click.echo_via_pager(tutorial_help(width=width))
def _show_photo(photo):
def _show_photo(photo: PhotoInfo):
"""open image with default image viewer
Note: This is for debugging only -- it will actually open any filetype which could
@@ -4078,16 +4105,34 @@ def _get_selected(photosdb):
return get_selected
def _spotlight_photo(photo: PhotoInfo):
photo_ = photoscript.Photo(photo.uuid)
photo_.spotlight()
@cli.command()
@DB_OPTION
@click.pass_obj
@click.pass_context
def repl(ctx, cli_obj, db):
"""Run interactive osxphotos shell"""
@click.option(
"--emacs",
required=False,
is_flag=True,
default=False,
help="Launch REPL with Emacs keybindings (default is vi bindings)",
)
def repl(ctx, cli_obj, db, emacs):
"""Run interactive osxphotos REPL shell (useful for debugging, prototyping, and inspecting your Photos library)"""
from osxphotos import PhotosDB, PhotoInfo, ExifTool
from objexplore import explore
from photoscript import Album, Photo, PhotosLibrary
from rich import inspect as _inspect
from osxphotos import ExifTool, PhotoInfo, PhotosDB
from osxphotos.albuminfo import AlbumInfo
from osxphotos.placeinfo import PlaceInfo
from osxphotos.queryoptions import QueryOptions
pretty.install()
print(f"python version: {sys.version}")
print(f"osxphotos version: {osxphotos._version.__version__}")
@@ -4102,24 +4147,21 @@ def repl(ctx, cli_obj, db):
# shortcut for helper functions
get_photo = photosdb.get_photo
show = _show_photo
spotlight = _spotlight_photo
get_selected = _get_selected(photosdb)
selected = get_selected()
try:
selected = get_selected()
except Exception:
# get_selected sometimes fails
selected = []
def inspect(obj):
"""inspect object"""
return _inspect(obj, methods=True)
class ReprQuit:
def __repr__(self):
sys.exit(0)
def __call__(self):
sys.exit(0)
quit = ReprQuit()
q = ReprQuit()
print(f"Found {len(photos)} photos in {tictoc:0.2f} seconds")
print(f"Found {len(photos)} photos in {tictoc:0.2f} seconds\n")
print("The following classes have been imported from osxphotos:")
print("- AlbumInfo, ExifTool, PhotoInfo, PhotosDB, PlaceInfo, QueryOptions\n")
print("The following variables are defined:")
print(f"- photosdb: PhotosDB() instance for {photosdb.library_path}")
print(
@@ -4129,16 +4171,34 @@ def repl(ctx, cli_obj, db):
f"- selected: list of PhotoInfo objects for any photos selected in Photos (len={len(selected)})"
)
print(f"\nThe following functions may be helpful:")
print(f"- get_photo(uuid): return a PhotoInfo object for photo with uuid")
print(
f"- get_selected(): return list of PhotoInfo objects for photos selected in Photos"
)
print(f"- show(photo): open a photo object in the default viewer")
print(
f"- help(object): print help text including list of methods for object; for example, help(PhotosDB)"
f"- get_photo(uuid): return a PhotoInfo object for photo with uuid; e.g. get_photo('B13F4485-94E0-41CD-AF71-913095D62E31')"
)
print(
f"- inspect(object): print information about an object; for example inspect(photosdb)"
f"- get_selected(); return list of PhotoInfo objects for photos selected in Photos"
)
print(
f"- show(photo): open a photo object in the default viewer; e.g. show(selected[0])"
)
print(
f"- show(path): open a file at path in the default viewer; e.g. show('/path/to/photo.jpg')"
)
print(f"- spotlight(photo): open a photo and spotlight it in Photos")
# print(
# f"- help(object): print help text including list of methods for object; for example, help(PhotosDB)"
# )
print(
f"- inspect(object): print information about an object; e.g. inspect(PhotoInfo)"
)
print(
f"- explore(object): interactively explore an object with objexplore; e.g. explore(PhotoInfo)"
)
print(f"- q, quit, quit(), exit, exit(): exit this interactive shell\n")
embed_repl(
globals=globals(),
locals=locals(),
history_filename=str(pathlib.Path.home() / ".osxphotos_repl_history"),
quit_words=["q", "quit", "exit"],
vi_mode=not emacs,
)
print(f"- q, quit, or quit(): exit this interactive shell\n")
code.interact(banner="", local=locals())

View File

@@ -7,7 +7,7 @@ import subprocess
import sys
from abc import ABC, abstractmethod
import CoreFoundation
import Foundation
from .imageconverter import ImageConverter
@@ -114,7 +114,7 @@ class FileUtilMacOS(FileUtilABC):
if dest.is_dir():
dest /= src.name
filemgr = CoreFoundation.NSFileManager.defaultManager()
filemgr = Foundation.NSFileManager.defaultManager()
error = filemgr.copyItemAtPath_toPath_error_(str(src), str(dest), None)
# error is a tuple of (bool, error_string)
# error[0] is True if copy succeeded

View File

@@ -141,7 +141,7 @@ class FaceInfo:
self.manual = face["manual"]
self.face_type = face["facetype"]
self.age_type = face["agetype"]
self.bald_type = face["baldtype"]
# self.bald_type = face["baldtype"]
self.eye_makeup_type = face["eyemakeuptype"]
self.eye_state = face["eyestate"]
self.facial_hair_type = face["facialhairtype"]
@@ -438,7 +438,7 @@ class FaceInfo:
"manual": self.manual,
"face_type": self.face_type,
"age_type": self.age_type,
"bald_type": self.bald_type,
# "bald_type": self.bald_type,
"eye_makeup_type": self.eye_makeup_type,
"eye_state": self.eye_state,
"facial_hair_type": self.facial_hair_type,

View File

@@ -912,7 +912,14 @@ def export2(
preview_path = pathlib.Path(self.path_derivatives[0])
preview_ext = preview_path.suffix
preview_name = dest.parent / f"{dest.stem}{preview_suffix}{preview_ext}"
preview_name = pathlib.Path(increment_filename(preview_name))
# if original is missing, the filename won't have been incremented so
# need to check here to make sure there aren't duplicate preview files in
# the export directory
preview_name = (
preview_name
if overwrite or update
else pathlib.Path(increment_filename(preview_name))
)
if preview_path is not None:
results = self._export_photo(
preview_path,
@@ -1259,6 +1266,7 @@ def _export_photo_with_photos_export(
dest.name,
version=PHOTOS_VERSION_CURRENT,
overwrite=overwrite,
video=live_photo,
)
all_results.exported.extend(exported)
except Exception as e:
@@ -1306,6 +1314,7 @@ def _export_photo_with_photos_export(
dest.name,
version=PHOTOS_VERSION_ORIGINAL,
overwrite=overwrite,
video=live_photo,
)
all_results.exported.extend(exported)
except Exception as e:

View File

@@ -38,6 +38,7 @@ from ..albuminfo import AlbumInfo, ImportInfo
from ..personinfo import FaceInfo, PersonInfo
from ..phototemplate import PhotoTemplate, RenderOptions
from ..placeinfo import PlaceInfo4, PlaceInfo5
from ..query_builder import get_query
from ..text_detection import detect_text
from ..uti import get_preferred_uti_extension, get_uti_for_extension
from ..utils import _debug, _get_resource_loc, findfiles
@@ -1098,6 +1099,26 @@ class PhotoInfo:
logging.warning(f"Did not find signature for {self.uuid} in _db_signatures")
return duplicates
@property
def owner(self):
"""Return name of photo owner for shared photos (Photos 5+ only), or None if not shared"""
if self._db._db_version <= _PHOTOS_4_VERSION:
return None
try:
return self._owner
except AttributeError:
try:
personid = self._info["cloudownerhashedpersonid"]
self._owner = (
self._db._db_hashed_person_id[personid]["full_name"]
if personid
else None
)
except KeyError:
self._owner = None
return self._owner
def render_template(
self, template_str: str, options: Optional[RenderOptions] = None
):

View File

@@ -6,8 +6,6 @@
"""
# NOTES:
# - This likely leaks memory like a sieve as I need to ensure all the
# Objective C objects are cleaned up.
# - There are several techniques used for handling PhotoKit's various
# asynchronous calls used in this code: event loop+notification, threading
# event, while loop. I've experimented with each to find the one that works.
@@ -200,16 +198,6 @@ class PHAssetResourceData:
self.data = b""
# class LivePhotoData:
# """ Simple class to hold the data passed to the handler for
# requestLivePhotoForAsset:targetSize:contentMode:options:resultHandler:
# """
# def __init__(self):
# self.live_photo = None
# self.info = None
class PhotoKitNotificationDelegate(NSObject):
"""Handles notifications from NotificationCenter;
used with asynchronous PhotoKit requests to stop event loop when complete
@@ -487,6 +475,7 @@ class PhotoAsset:
version=PHOTOS_VERSION_CURRENT,
overwrite=False,
raw=False,
**kwargs,
):
"""Export image to path
@@ -496,6 +485,7 @@ class PhotoAsset:
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
overwrite: bool, if True, overwrites destination file if it already exists; default is False
raw: bool, if True, export RAW component of RAW+JPEG pair, default is False
**kwargs: used only to avoid issues with each asset type having slightly different export arguments
Returns:
List of path to exported image(s)
@@ -504,9 +494,6 @@ class PhotoAsset:
ValueError if dest is not a valid directory
"""
# if self.live:
# raise NotImplementedError("Live photos not implemented yet")
with objc.autorelease_pool():
filename = (
pathlib.Path(filename)
@@ -615,9 +602,7 @@ class PhotoAsset:
nonlocal requestdata
options = {}
# pylint: disable=no-member
options[Quartz.kCGImageSourceShouldCache] = Foundation.kCFBooleanFalse
options = {Quartz.kCGImageSourceShouldCache: Foundation.kCFBooleanFalse}
imgSrc = Quartz.CGImageSourceCreateWithData(imageData, options)
requestdata.metadata = Quartz.CGImageSourceCopyPropertiesAtIndex(
imgSrc, 0, options
@@ -701,9 +686,7 @@ class PhotoAsset:
nonlocal data
options = {}
# pylint: disable=no-member
options[Quartz.kCGImageSourceShouldCache] = Foundation.kCFBooleanFalse
options = {Quartz.kCGImageSourceShouldCache: Foundation.kCFBooleanFalse}
imgSrc = Quartz.CGImageSourceCreateWithData(imageData, options)
data.metadata = Quartz.CGImageSourceCopyPropertiesAtIndex(
imgSrc, 0, options
@@ -789,7 +772,6 @@ class SlowMoVideoExporter(NSObject):
self.url = None
self.done = None
self.nc = None
# super(NSObject, self).dealloc()
class VideoAsset(PhotoAsset):
@@ -801,7 +783,12 @@ class VideoAsset(PhotoAsset):
# https://developer.apple.com/documentation/photokit/phimagemanager/1616981-requestexportsessionforvideo?language=objc
# above 10.15 only
def export(
self, dest, filename=None, version=PHOTOS_VERSION_CURRENT, overwrite=False
self,
dest,
filename=None,
version=PHOTOS_VERSION_CURRENT,
overwrite=False,
**kwargs,
):
"""Export video to path
@@ -810,6 +797,7 @@ class VideoAsset(PhotoAsset):
filename: str, optional name of exported file; if not provided, defaults to asset's original filename
version: which version of image (PHOTOS_VERSION_ORIGINAL or PHOTOS_VERSION_CURRENT)
overwrite: bool, if True, overwrites destination file if it already exists; default is False
**kwargs: used only to avoid issues with each asset type having slightly different export arguments
Returns:
List of path to exported image(s)
@@ -1043,6 +1031,7 @@ class LivePhotoAsset(PhotoAsset):
overwrite=False,
photo=True,
video=True,
**kwargs,
):
"""Export image to path
@@ -1053,6 +1042,7 @@ class LivePhotoAsset(PhotoAsset):
overwrite: bool, if True, overwrites destination file if it already exists; default is False
photo: bool, if True, export photo component of live photo
video: bool, if True, export live video component of live photo
**kwargs: used only to avoid issues with each asset type having slightly different export arguments
Returns:
list of [path to exported image and/or video]
@@ -1104,39 +1094,6 @@ class LivePhotoAsset(PhotoAsset):
photo_output_file = pathlib.Path(increment_filename(photo_output_file))
video_output_file = pathlib.Path(increment_filename(video_output_file))
# def handler(error):
# if error:
# raise PhotoKitExportError(f"writeDataForAssetResource error: {error}")
# resource_manager = Photos.PHAssetResourceManager.defaultManager()
# options = Photos.PHAssetResourceRequestOptions.alloc().init()
# options.setNetworkAccessAllowed_(True)
# exported = []
# Note: Tried writeDataForAssetResource_toFile_options_completionHandler_ which works
# but sets quarantine flag and for reasons I can't determine (maybe quarantine flag)
# causes pathlib.Path().is_file() to fail in tests
# if photo:
# photo_output_url = path_to_NSURL(photo_output_file)
# resource_manager.writeDataForAssetResource_toFile_options_completionHandler_(
# photo_resource, photo_output_url, options, handler
# )
# exported.append(str(photo_output_file))
# if video:
# video_output_url = path_to_NSURL(video_output_file)
# resource_manager.writeDataForAssetResource_toFile_options_completionHandler_(
# video_resource, video_output_url, options, handler
# )
# exported.append(str(video_output_file))
# def completion_handler(error):
# if error:
# raise PhotoKitExportError(f"writeDataForAssetResource error: {error}")
# would be nice to be able to usewriteDataForAssetResource_toFile_options_completionHandler_
# but it sets quarantine flags that cause issues so instead, request the data and write the files directly
exported = []
if photo:
data = self._request_resource_data(photo_resource)
@@ -1155,41 +1112,6 @@ class LivePhotoAsset(PhotoAsset):
request.dealloc()
return exported
# def request_image_data(self, version=PHOTOS_VERSION_CURRENT):
# # Returns an NSImage which isn't overly useful
# # https://developer.apple.com/documentation/photokit/phimagemanager/1616964-requestimageforasset?language=objc
# # requestImageForAsset:targetSize:contentMode:options:resultHandler:
# options = Photos.PHImageRequestOptions.alloc().init()
# options.setVersion_(version)
# options.setNetworkAccessAllowed_(True)
# options.setSynchronous_(True)
# options.setDeliveryMode_(
# Photos.PHImageRequestOptionsDeliveryModeHighQualityFormat
# )
# event = threading.Event()
# image_data = ImageData()
# def handler(result, info):
# nonlocal image_data
# if not info["PHImageResultIsDegradedKey"]:
# image_data.image_data = result
# image_data.info = info
# event.set()
# self._manager.requestImageForAsset_targetSize_contentMode_options_resultHandler_(
# self._phasset,
# Photos.PHImageManagerMaximumSize,
# Photos.PHImageContentModeDefault,
# options,
# handler,
# )
# event.wait()
# options.dealloc()
# return image_data
class PhotoLibrary:
"""Interface to PhotoKit PHImageManager and PHPhotoLibrary"""

View File

@@ -70,12 +70,24 @@ def _process_comments_5(photosdb):
results = conn.execute(
"""
SELECT DISTINCT
ZINVITEEHASHEDPERSONID,
ZINVITEEFIRSTNAME,
ZINVITEELASTNAME,
ZINVITEEFULLNAME
FROM
ZCLOUDSHAREDALBUMINVITATIONRECORD
ZINVITEEHASHEDPERSONID AS HASHEDPERSONID,
ZINVITEEFIRSTNAME AS FIRSTNAME,
ZINVITEELASTNAME AS LASTNAME,
ZINVITEEFULLNAME AS FULLNAME
FROM ZCLOUDSHAREDALBUMINVITATIONRECORD
WHERE HASHEDPERSONID IS NOT NULL
AND HASHEDPERSONID != ""
AND NOT (FIRSTNAME IS NULL AND LASTNAME IS NULL)
UNION
SELECT DISTINCT
ZCLOUDOWNERHASHEDPERSONID AS HASHEDPERSONID,
ZCLOUDOWNERFIRSTNAME AS FIRSTNAME,
ZCLOUDOWNERLASTNAME AS LASTNAME,
ZCLOUDOWNERFULLNAME AS FULLNAME
FROM ZGENERICALBUM
WHERE HASHEDPERSONID IS NOT NULL
AND HASHEDPERSONID != ""
AND NOT (FIRSTNAME IS NULL AND LASTNAME IS NULL)
"""
)
@@ -148,10 +160,10 @@ def _process_comments_5(photosdb):
db_comments["comments"].append(CommentInfo(dt, user_name, ismine, text))
# sort results
for uuid in photosdb._db_comments_uuid:
for uuid, value in photosdb._db_comments_uuid.items():
if photosdb._db_comments_uuid[uuid]["likes"]:
photosdb._db_comments_uuid[uuid]["likes"].sort(key=lambda x: x.datetime)
if photosdb._db_comments_uuid[uuid]["comments"]:
photosdb._db_comments_uuid[uuid]["comments"].sort(key=lambda x: x.datetime)
value["comments"].sort(key=lambda x: x.datetime)
conn.close()

View File

@@ -146,7 +146,6 @@ def _process_faceinfo_4(photosdb):
# Photos 5 only
face["agetype"] = None
face["baldtype"] = None
face["eyemakeuptype"] = None
face["eyestate"] = None
face["facialhairtype"] = None
@@ -194,7 +193,7 @@ def _process_faceinfo_5(photosdb):
ZDETECTEDFACE.ZPERSON,
ZPERSON.ZFULLNAME,
ZDETECTEDFACE.ZAGETYPE,
ZDETECTEDFACE.ZBALDTYPE,
NULL, -- ZDETECTEDFACE.ZBALDTYPE (Removed in Monterey)
ZDETECTEDFACE.ZEYEMAKEUPTYPE,
ZDETECTEDFACE.ZEYESSTATE,
ZDETECTEDFACE.ZFACIALHAIRTYPE,
@@ -239,7 +238,7 @@ def _process_faceinfo_5(photosdb):
# 3 ZDETECTEDFACE.ZPERSON,
# 4 ZPERSON.ZFULLNAME,
# 5 ZDETECTEDFACE.ZAGETYPE,
# 6 ZDETECTEDFACE.ZBALDTYPE,
# 6 ZDETECTEDFACE.ZBALDTYPE, (Not available on Monterey)
# 7 ZDETECTEDFACE.ZEYEMAKEUPTYPE,
# 8 ZDETECTEDFACE.ZEYESSTATE,
# 9 ZDETECTEDFACE.ZFACIALHAIRTYPE,
@@ -284,7 +283,6 @@ def _process_faceinfo_5(photosdb):
face["person"] = person_pk
face["fullname"] = normalize_unicode(row[4])
face["agetype"] = row[5]
face["baldtype"] = row[6]
face["eyemakeuptype"] = row[7]
face["eyestate"] = row[8]
face["facialhairtype"] = row[9]

View File

@@ -330,6 +330,8 @@ class PhotosDB:
else:
self._process_database5()
self._db_connection, _ = self.get_db_connection()
@property
def keywords_as_dict(self):
"""return keywords as dict of keyword, count in reverse sorted order (descending)"""
@@ -790,8 +792,8 @@ class PhotosDB:
"creation_date": album[8],
"start_date": None, # Photos 5 only
"end_date": None, # Photos 5 only
"customsortascending": None, # Photos 5 only
"customsortkey": None, # Photos 5 only
"customsortascending": None, # Photos 5 only
"customsortkey": None, # Photos 5 only
}
# get details about folders
@@ -1104,7 +1106,9 @@ class PhotosDB:
# get info on special types
self._dbphotos[uuid]["specialType"] = row[25]
self._dbphotos[uuid]["masterModelID"] = row[26]
self._dbphotos[uuid]["pk"] = row[26] # same as masterModelID, to match Photos 5
self._dbphotos[uuid]["pk"] = row[
26
] # same as masterModelID, to match Photos 5
self._dbphotos[uuid]["panorama"] = True if row[25] == 1 else False
self._dbphotos[uuid]["slow_mo"] = True if row[25] == 2 else False
self._dbphotos[uuid]["time_lapse"] = True if row[25] == 3 else False
@@ -1195,6 +1199,9 @@ class PhotosDB:
self._dbphotos[uuid]["import_uuid"] = row[44]
self._dbphotos[uuid]["fok_import_session"] = None
# photos 5+ only, for shared photos
self._dbphotos[uuid]["cloudownerhashedpersonid"] = None
# compute signatures for finding possible duplicates
signature = self._duplicate_signature(uuid)
try:
@@ -1923,7 +1930,8 @@ class PhotosDB:
{asset_table}.ZTRASHEDDATE,
{asset_table}.ZSAVEDASSETTYPE,
{asset_table}.ZADDEDDATE,
{asset_table}.Z_PK
{asset_table}.Z_PK,
{asset_table}.ZCLOUDOWNERHASHEDPERSONID
FROM {asset_table}
JOIN ZADDITIONALASSETATTRIBUTES ON ZADDITIONALASSETATTRIBUTES.ZASSET = {asset_table}.Z_PK
ORDER BY {asset_table}.ZUUID """
@@ -1973,6 +1981,7 @@ class PhotosDB:
# 40 ZGENERICASSET.ZSAVEDASSETTYPE -- how item imported
# 41 ZGENERICASSET.ZADDEDDATE -- date item added to the library
# 42 ZGENERICASSET.Z_PK -- primary key
# 43 ZGENERICASSET.ZCLOUDOWNERHASHEDPERSONID -- used to look up owner name (for shared photos)
for row in c:
uuid = row[0]
@@ -2158,6 +2167,7 @@ class PhotosDB:
info["added_date"] = datetime(1970, 1, 1)
info["pk"] = row[42]
info["cloudownerhashedpersonid"] = row[43]
# initialize import session info which will be filled in later
# not every photo has an import session so initialize all records now
@@ -3286,9 +3296,9 @@ class PhotosDB:
if options.regex:
flags = re.IGNORECASE if options.ignore_case else 0
render_options = RenderOptions(none_str="")
photo_list = []
for regex, template in options.regex:
regex = re.compile(regex, flags)
photo_list = []
for p in photos:
rendered, _ = p.render_template(template, render_options)
for value in rendered:
@@ -3354,6 +3364,10 @@ class PhotosDB:
return photos
def execute(self, sql):
"""Execute sql statement and return cursor"""
return self._db_connection.cursor().execute(sql)
def _duplicate_signature(self, uuid):
"""Compute a signature for finding possible duplicates"""
return (
@@ -3381,6 +3395,10 @@ class PhotosDB:
"""
return len(self._dbphotos)
def __del__(self):
if getattr(self, "_db_connection", None):
self._db_connection.close()
def _get_photos_by_attribute(photos, attribute, values, ignore_case):
"""Search for photos based on values being in PhotoInfo.attribute

View File

@@ -1,4 +1,4 @@
The templating system converts one or template statements, written in osxphotos templating language, to one or more rendered values using information from the photo being processed.
The templating system converts one or template statements, written in osxphotos metadata templating language, to one or more rendered values using information from the photo being processed.
In its simplest form, a template statement has the form: `"{template_field}"`, for example `"{title}"` which would resolve to the title of the photo.

View File

@@ -1,4 +1,4 @@
""" Custom template system for osxphotos, implements osxphotos template language (OTL) """
""" Custom template system for osxphotos, implements metadata template language (MTL) """
import datetime
import json
@@ -27,7 +27,7 @@ from .utils import expand_and_validate_filepath, load_function
# ensure locale set to user's locale
locale.setlocale(locale.LC_ALL, "")
OTL_GRAMMAR_MODEL = str(pathlib.Path(__file__).parent / "phototemplate.tx")
MTL_GRAMMAR_MODEL = str(pathlib.Path(__file__).parent / "phototemplate.tx")
"""TextX metamodel for osxphotos template language """
@@ -324,7 +324,7 @@ class PhotoTemplateParser:
if hasattr(self, "metamodel"):
return
self.metamodel = metamodel_from_file(OTL_GRAMMAR_MODEL, skipws=False)
self.metamodel = metamodel_from_file(MTL_GRAMMAR_MODEL, skipws=False)
def parse(self, template_statement):
"""Parse a template_statement string"""

View File

@@ -1,4 +1,4 @@
// OSXPhotos Template Language (OTL)
// OSXPhotos Metadata Template Language (MTL)
// a TemplateString has format:
// pre{delim+template_field:subfield|filter(path_sep)[find,replace] conditional?bool_value,default}post
// a TemplateStatement may contain zero or more TemplateStrings

131
osxphotos/pyrepl.py Normal file
View File

@@ -0,0 +1,131 @@
""" Custom Python REPL based on ptpython that allows quitting with custom keywords instead of `quit()` """
""" This file is distributed under the same license as the ptpython package:
Copyright (c) 2015, Jonathan Slenders (ptpython), (c) 2021 Rhet Turnbull (this file)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
* Neither the name of the {organization} nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import sys
from typing import Callable, List, Optional
from ptpython.repl import (
ContextManager,
DummyContext,
PythonRepl,
builtins,
patch_stdout_context,
)
class PyReplQuitter(PythonRepl):
"""Custom pypython repl that allows quitting REPL with custom commands"""
def __init__(self, *args, quit_words: Optional[List[str]] = None, **kwargs):
super().__init__(*args, **kwargs)
self.quit_words = quit_words or ["quit", "q"]
def eval(self, line: str) -> object:
if line.strip() in self.quit_words:
sys.exit(0)
return super().eval(line)
def embed_repl(
globals=None,
locals=None,
configure: Optional[Callable[[PythonRepl], None]] = None,
vi_mode: bool = False,
history_filename: Optional[str] = None,
title: Optional[str] = None,
startup_paths=None,
patch_stdout: bool = False,
return_asyncio_coroutine: bool = False,
quit_words: Optional[List[str]] = None,
) -> None:
"""
Call this to embed Python shell at the current point in your program.
It's similar to `IPython.embed` and `bpython.embed`. ::
from prompt_toolkit.contrib.repl import embed
embed(globals(), locals())
:param vi_mode: Boolean. Use Vi instead of Emacs key bindings.
:param configure: Callable that will be called with the `PythonRepl` as a first
argument, to trigger configuration.
:param title: Title to be displayed in the terminal titlebar. (None or string.)
:param patch_stdout: When true, patch `sys.stdout` so that background
threads that are printing will print nicely above the prompt.
"""
# Default globals/locals
if globals is None:
globals = {
"__name__": "__main__",
"__package__": None,
"__doc__": None,
"__builtins__": builtins,
}
locals = locals or globals
def get_globals():
return globals
def get_locals():
return locals
# Create REPL.
repl = PyReplQuitter(
get_globals=get_globals,
get_locals=get_locals,
vi_mode=vi_mode,
history_filename=history_filename,
startup_paths=startup_paths,
quit_words=quit_words,
)
if title:
repl.terminal_title = title
if configure:
configure(repl)
# Start repl.
patch_context: ContextManager = (
patch_stdout_context() if patch_stdout else DummyContext()
)
if return_asyncio_coroutine:
async def coroutine():
with patch_context:
await repl.run_async()
return coroutine()
else:
with patch_context:
repl.run()

View File

@@ -0,0 +1,5 @@
# Query templates
This folder contains sql query templates for getting various photo properties
The query templates must be rendered with mako (see query_builder.py)

View File

@@ -0,0 +1,4 @@
-- Get owner name for shared iCloud album
SELECT ZGENERICALBUM.ZCLOUDOWNERFULLNAME AS OWNER_FULLNAME
FROM ZGENERICALBUM
WHERE ZGENERICALBUM.ZUUID = '${uuid}'

View File

@@ -0,0 +1,23 @@
-- Get the owner name of person who owns a photo in a shared album
--
-- Case where someone has invited you to a shared album
-- Need to get the owner of the shared album
SELECT DISTINCT
ZGENERICALBUM.ZCLOUDOWNERFULLNAME as OWNER_FULLNAME
FROM ZGENERICALBUM
JOIN ${asset_table} ON ${asset_table}.ZCLOUDOWNERHASHEDPERSONID = ZGENERICALBUM.ZCLOUDOWNERHASHEDPERSONID
WHERE ${asset_table}.ZUUID = "${uuid}"
AND ZGENERICALBUM.ZCLOUDOWNERHASHEDPERSONID IS NOT NULL
AND ZGENERICALBUM.ZCLOUDOWNERHASHEDPERSONID != ""
AND OWNER_FULLNAME != "(null) (null)"
UNION
-- Case where you have invited someone to a shared album
-- Need to get the data for person who was invited to the album
SELECT DISTINCT
ZCLOUDSHAREDALBUMINVITATIONRECORD.ZINVITEEFULLNAME AS OWNER_FULLNAME
FROM ZCLOUDSHAREDALBUMINVITATIONRECORD
JOIN ${asset_table} ON ${asset_table}.ZCLOUDOWNERHASHEDPERSONID = ZCLOUDSHAREDALBUMINVITATIONRECORD.ZINVITEEHASHEDPERSONID
WHERE ${asset_table}.ZUUID = "${uuid}"
AND ZCLOUDSHAREDALBUMINVITATIONRECORD.ZINVITEEHASHEDPERSONID IS NOT NULL
AND ZCLOUDSHAREDALBUMINVITATIONRECORD.ZINVITEEHASHEDPERSONID != ""
AND OWNER_FULLNAME != "(null) (null)"

View File

@@ -0,0 +1,6 @@
-- Get title of a photo with given UUID
SELECT
ZADDITIONALASSETATTRIBUTES.ZTITLE
FROM ZADDITIONALASSETATTRIBUTES
JOIN ${asset_table} ON ${asset_table}.Z_PK = ZADDITIONALASSETATTRIBUTES.ZASSET
WHERE ${asset_table}.ZUUID = "${uuid}"

View File

@@ -0,0 +1,36 @@
"""Build sql queries from template to retrieve info from the database"""
import os.path
import pathlib
from functools import lru_cache
from mako.template import Template
from ._constants import _DB_TABLE_NAMES
QUERY_DIR = os.path.join(os.path.dirname(__file__), "queries")
def get_query(query_name, photos_ver, **kwargs):
"""Return sqlite query string for an attribute and a given database version"""
# there can be a single query for multiple database versions or separate queries for each version
# try generic version first (most common case), if that fails, look for version specific query
query_string = _get_query_string(query_name, photos_ver)
asset_table = _DB_TABLE_NAMES[photos_ver]["ASSET"]
query_template = Template(query_string)
return query_template.render(asset_table=asset_table, **kwargs)
@lru_cache(maxsize=None)
def _get_query_string(query_name, photos_ver):
"""Return sqlite query string for an attribute and a given database version"""
query_file = pathlib.Path(QUERY_DIR) / f"{query_name}.sql.mako"
if not query_file.is_file():
query_file = pathlib.Path(QUERY_DIR) / f"{query_name}_{photos_ver}.sql.mako"
if not query_file.is_file():
raise FileNotFoundError(f"Query file '{query_file}' not found")
with open(query_file, "r") as f:
query_string = f.read()
return query_string

View File

@@ -1,23 +1,26 @@
pyobjc-core>=7.2,<8.0
pyobjc-framework-AppleScriptKit>=7.2,<8.0
pyobjc-framework-AppleScriptObjC>=7.2,<8.0
pyobjc-framework-Photos>=7.2,<8.0
pyobjc-framework-Quartz>=7.2,<8.0
pyobjc-framework-AVFoundation>=7.2,<8.0
pyobjc-framework-CoreServices>=7.2,<8.0
pyobjc-framework-Metal>=7.2,<8.0
pyobjc-framework-Vision>=7.2,<8.0
Click>=8.0.1,<9.0
PyYAML>=5.4.1<5.5.0
Mako>=1.1.4,<1.2.0
bpylist2==3.0.2
pathvalidate>=2.4.1,<2.5.0
dataclasses==0.7;python_version<'3.7'
wurlitzer>=2.1.0,<2.2.0
photoscript>=0.1.4,<0.2.0
toml>=0.10.2,<0.11.0
osxmetadata>=0.99.33,<1.0.0
textx>=2.3.0,<2.4.0
rich>=10.6.0,<11.0.0
PyYAML>=5.4.1<5.5.0
bitmath>=1.3.3.1,<1.4.0.0
bpylist2==3.0.2
dataclasses==0.7;python_version<'3.7'
more-itertools>=8.8.0,<9.0.0
objexplore>=1.5.5,<1.6.0
osxmetadata>=0.99.34,<1.0.0
pathvalidate>=2.4.1,<2.5.0
photoscript>=0.1.4,<0.2.0
ptpython>=3.0.20,<3.1.0
pyobjc-core>=7.3,<9.0
pyobjc-framework-AVFoundation>=7.3,<9.0
pyobjc-framework-AppleScriptKit>=7.3,<9.0
pyobjc-framework-AppleScriptObjC>=7.3,<9.0
pyobjc-framework-Cocoa>=7.3,<9.0
pyobjc-framework-CoreServices>=7.2,<9.0
pyobjc-framework-Metal>=7.3,<9.0
pyobjc-framework-Photos>=7.3,<9.0
pyobjc-framework-Quartz>=7.3,<9.0
pyobjc-framework-Vision>=7.3,<9.0
rich>=10.6.0,<=11.0.0
textx>=2.3.0,<2.4.0
toml>=0.10.2,<0.11.0
wurlitzer>=2.1.0,<2.2.0

View File

@@ -1,8 +1,9 @@
sphinx_click
pytest==6.2.4
pytest-mock
build
m2r2
pyinstaller==4.4
pytest-mock
pytest==6.2.4
sphinx_click
sphinx_rtd_theme
wheel
twine
wheel

View File

@@ -70,32 +70,36 @@ setup(
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Topic :: Software Development :: Libraries :: Python Modules",
],
install_requires=[
"pyobjc-core>=7.2,<8.0",
"pyobjc-framework-AppleScriptKit>=7.2,<8.0",
"pyobjc-framework-AppleScriptObjC>=7.2,<8.0",
"pyobjc-framework-Photos>=7.2,<8.0",
"pyobjc-framework-Quartz>=7.2,<8.0",
"pyobjc-framework-AVFoundation>=7.2,<8.0",
"pyobjc-framework-CoreServices>=7.2,<8.0",
"pyobjc-framework-Metal>=7.2,<8.0",
"pyobjc-framework-Vision>=7.2,<8.0",
"Click>=8.0.1,<9.0",
"PyYAML>=5.4.1,<5.5.0",
"Mako>=1.1.4,<1.2.0",
"bpylist2==3.0.2",
"pathvalidate>=2.4.1,<2.5.0",
"dataclasses==0.7;python_version<'3.7'",
"wurlitzer>=2.1.0,<2.2.0",
"photoscript>=0.1.4,<0.2.0",
"toml>=0.10.2,<0.11.0",
"osxmetadata>=0.99.33,<1.0.0",
"textx>=2.3.0,<2.4.0",
"rich>=10.6.0,<11.0.0",
"PyYAML>=5.4.1,<5.5.0",
"bitmath>=1.3.3.1,<1.4.0.0",
"bpylist2==3.0.2",
"dataclasses==0.7;python_version<'3.7'",
"more-itertools>=8.8.0,<9.0.0",
"objexplore>=1.5.5,<1.6.0",
"osxmetadata>=0.99.34,<1.0.0",
"pathvalidate>=2.4.1,<3.0.0",
"photoscript>=0.1.4,<0.2.0",
"ptpython>=3.0.20,<4.0.0",
"pyobjc-core>=7.3,<9.0",
"pyobjc-framework-AVFoundation>=7.3,<9.0",
"pyobjc-framework-AppleScriptKit>=7.3,<9.0",
"pyobjc-framework-AppleScriptObjC>=7.3,<9.0",
"pyobjc-framework-Cocoa>=7.3,<9.0",
"pyobjc-framework-CoreServices>=7.2,<9.0",
"pyobjc-framework-Metal>=7.3,<9.0",
"pyobjc-framework-Photos>=7.3,<9.0",
"pyobjc-framework-Quartz>=7.3,<9.0",
"pyobjc-framework-Vision>=7.3,<9.0",
"rich>=10.6.0,<=11.0.0",
"textx>=2.3.0,<3.0.0",
"toml>=0.10.2,<0.11.0",
"wurlitzer>=2.1.0,<3.0.0",
],
entry_points={"console_scripts": ["osxphotos=osxphotos.__main__:cli"]},
include_package_data=True,

View File

@@ -337,7 +337,7 @@ def test_attributes(photosdb):
def test_attributes_2(photosdb):
""" Test attributes including height, width, etc """
"""Test attributes including height, width, etc"""
import datetime
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
@@ -517,39 +517,39 @@ def test_count(photosdb):
def test_photos_intrash_1(photosdb):
""" test PhotosDB.photos(intrash=True) """
"""test PhotosDB.photos(intrash=True)"""
photos = photosdb.photos(intrash=True)
assert len(photos) == PHOTOS_IN_TRASH_LEN
def test_photos_intrash_2(photosdb):
""" test PhotosDB.photos(intrash=True) """
"""test PhotosDB.photos(intrash=True)"""
photos = photosdb.photos(intrash=True)
for p in photos:
assert p.intrash
def test_photos_intrash_3(photosdb):
""" test PhotosDB.photos(intrash=False) """
"""test PhotosDB.photos(intrash=False)"""
photos = photosdb.photos(intrash=False)
for p in photos:
assert not p.intrash
def test_photoinfo_intrash_1(photosdb):
""" Test PhotoInfo.intrash """
"""Test PhotoInfo.intrash"""
p = photosdb.photos(uuid=[UUID_DICT["intrash"]], intrash=True)[0]
assert p.intrash
def test_photoinfo_intrash_2(photosdb):
""" Test PhotoInfo.intrash and intrash=default"""
"""Test PhotoInfo.intrash and intrash=default"""
p = photosdb.photos(uuid=[UUID_DICT["intrash"]])
assert not p
def test_photoinfo_intrash_3(photosdb):
""" Test PhotoInfo.intrash and photo has keyword and person """
"""Test PhotoInfo.intrash and photo has keyword and person"""
p = photosdb.photos(uuid=[UUID_DICT["intrash_person_keywords"]], intrash=True)[0]
assert p.intrash
assert "Maria" in p.persons
@@ -557,7 +557,7 @@ def test_photoinfo_intrash_3(photosdb):
def test_photoinfo_intrash_4(photosdb):
""" Test PhotoInfo.intrash and photo has keyword and person """
"""Test PhotoInfo.intrash and photo has keyword and person"""
p = photosdb.photos(persons=["Maria"], intrash=True)[0]
assert p.intrash
assert "Maria" in p.persons
@@ -565,7 +565,7 @@ def test_photoinfo_intrash_4(photosdb):
def test_photoinfo_intrash_5(photosdb):
""" Test PhotoInfo.intrash and photo has keyword and person """
"""Test PhotoInfo.intrash and photo has keyword and person"""
p = photosdb.photos(keywords=["wedding"], intrash=True)[0]
assert p.intrash
assert "Maria" in p.persons
@@ -573,7 +573,7 @@ def test_photoinfo_intrash_5(photosdb):
def test_photoinfo_not_intrash(photosdb):
""" Test PhotoInfo.intrash """
"""Test PhotoInfo.intrash"""
p = photosdb.photos(uuid=[UUID_DICT["not_intrash"]])[0]
assert not p.intrash
@@ -594,7 +594,7 @@ def test_keyword_not_in_album(photosdb):
def test_album_folder_name(photosdb):
"""Test query with album name same as a folder name """
"""Test query with album name same as a folder name"""
photos = photosdb.photos(albums=["Pumpkin Farm"])
assert sorted(p.uuid for p in photos) == sorted(UUID_PUMPKIN_FARM)
@@ -617,7 +617,7 @@ def test_get_library_path(photosdb):
def test_get_db_connection(photosdb):
""" Test PhotosDB.get_db_connection """
"""Test PhotosDB.get_db_connection"""
import sqlite3
conn, cursor = photosdb.get_db_connection()
@@ -926,7 +926,7 @@ def test_export_14(caplog, photosdb):
def test_eq(photosdb):
""" Test equality of two PhotoInfo objects """
"""Test equality of two PhotoInfo objects"""
import osxphotos
photosdb2 = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
@@ -936,7 +936,7 @@ def test_eq(photosdb):
def test_eq_2(photosdb):
""" Test equality of two PhotoInfo objects when one has memoized property """
"""Test equality of two PhotoInfo objects when one has memoized property"""
import osxphotos
photosdb2 = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
@@ -960,7 +960,7 @@ def test_photosdb_repr():
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photosdb2 = eval(repr(photosdb))
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name"]
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name", "_db_connection"]
assert {k: v for k, v in photosdb.__dict__.items() if k not in ignore_keys} == {
k: v for k, v in photosdb2.__dict__.items() if k not in ignore_keys
}
@@ -999,7 +999,7 @@ def test_from_to_date(photosdb):
def test_date_invalid():
""" Test date is invalid """
"""Test date is invalid"""
# doesn't run correctly with the module-level fixture
from datetime import datetime, timedelta, timezone
import osxphotos
@@ -1016,7 +1016,7 @@ def test_date_invalid():
def test_date_modified_invalid(photosdb):
""" Test date modified is invalid """
"""Test date modified is invalid"""
from datetime import datetime, timedelta, timezone
# UUID_DICT["date_invalid"] has an invalid modified date that's
@@ -1028,7 +1028,7 @@ def test_date_modified_invalid(photosdb):
def test_uti(photosdb):
""" test uti """
"""test uti"""
for uuid, uti in UTI_DICT.items():
photo = photosdb.get_photo(uuid)
@@ -1037,7 +1037,7 @@ def test_uti(photosdb):
def test_raw(photosdb):
""" Test various raw properties """
"""Test various raw properties"""
for uuid, rawinfo in RAW_DICT.items():
photo = photosdb.get_photo(uuid)
@@ -1050,7 +1050,7 @@ def test_raw(photosdb):
def test_is_reference(photosdb):
""" test isreference """
"""test isreference"""
photo = photosdb.get_photo(UUID_IS_REFERENCE)
assert photo.isreference
@@ -1059,7 +1059,7 @@ def test_is_reference(photosdb):
def test_adjustments(photosdb):
""" test adjustments/AdjustmentsInfo """
"""test adjustments/AdjustmentsInfo"""
from osxphotos.adjustmentsinfo import AdjustmentsInfo
photo = photosdb.get_photo(UUID_DICT["adjustments_info"])
@@ -1121,7 +1121,7 @@ def test_adjustments(photosdb):
def test_no_adjustments(photosdb):
""" test adjustments when photo has no adjusments"""
"""test adjustments when photo has no adjusments"""
photo = photosdb.get_photo(UUID_DICT["no_adjustments"])
assert photo.adjustments is None

View File

@@ -252,7 +252,7 @@ UUID_NOT_REFERENCE = "F12384F6-CD17-4151-ACBA-AE0E3688539E"
UUID_DUPLICATE = ""
UUID_DETECTED_TEXT = {
"E2078879-A29C-4D6F-BACB-E3BBE6C3EB91": "osxphotos",
"E2078879-A29C-4D6F-BACB-E3BBE6C3EB91": "OPEN",
"A92D9C26-3A50-4197-9388-CB5F7DB9FA91": None,
}
@@ -1066,7 +1066,7 @@ def test_photosdb_repr():
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photosdb2 = eval(repr(photosdb))
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name"]
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name", "_db_connection"]
assert {k: v for k, v in photosdb.__dict__.items() if k not in ignore_keys} == {
k: v for k, v in photosdb2.__dict__.items() if k not in ignore_keys
}

View File

@@ -40,6 +40,11 @@ UUID_BURST_ALBUM = {
],
}
UUID_SKIP_LIVE_PHOTOKIT = {
"54A01B04-16D7-4FDE-8860-19F2A641E433": ["IMG_3203_edited.jpeg"],
"1F3DF341-B822-4531-999E-724D642FD8E7": ["IMG_4179.jpeg"],
}
UUID_DOWNLOAD_MISSING = "C6C712C5-9316-408D-A3C3-125661422DA9" # IMG_8844.JPG
UUID_FILE = "tests/uuid_from_file.txt"
@@ -1483,6 +1488,94 @@ def test_export_preview_if_missing():
assert sorted(files) == sorted(expected_files)
def test_export_preview_overwrite():
"""test export with --preview and --overwrite (#526)"""
import glob
import os
import os.path
import osxphotos
from osxphotos.cli import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--preview",
"--uuid",
CLI_EXPORT_UUID,
],
)
assert result.exit_code == 0
# export again
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--preview",
"--uuid",
CLI_EXPORT_UUID,
"--overwrite",
],
)
assert result.exit_code == 0
files = glob.glob("*")
assert len(files) == 2 # preview + original
def test_export_preview_update():
"""test export with --preview and --update (#526)"""
import glob
import os
import os.path
import osxphotos
from osxphotos.cli import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--preview",
"--uuid",
CLI_EXPORT_UUID,
],
)
assert result.exit_code == 0
# export again
result = runner.invoke(
export,
[
os.path.join(cwd, CLI_PHOTOS_DB),
".",
"-V",
"--preview",
"--uuid",
CLI_EXPORT_UUID,
"--update",
],
)
assert result.exit_code == 0
files = glob.glob("*")
assert len(files) == 2 # preview + original
def test_export_as_hardlink():
import glob
import os
@@ -6557,6 +6650,43 @@ def test_export_download_missing_file_exists():
assert "exported: 1" in result.output
@pytest.mark.skipif(
"OSXPHOTOS_TEST_EXPORT" not in os.environ,
reason="Skip if not running on author's personal library.",
)
def test_export_skip_live_photokit():
"""test that --skip-live works with --use-photokit (issue #537)"""
import os
import os.path
import pathlib
from osxphotos.cli import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
for uuid in UUID_SKIP_LIVE_PHOTOKIT:
with runner.isolated_filesystem():
result = runner.invoke(
export,
[
os.path.join(cwd, PHOTOS_DB_RHET),
".",
"-V",
"--uuid",
uuid,
"--use-photos-export",
"--use-photokit",
"--skip-live",
"--skip-original-if-edited",
"--convert-to-jpeg",
],
)
assert result.exit_code == 0
files = [str(p) for p in pathlib.Path(".").glob("IMG*")]
assert sorted(files) == sorted(UUID_SKIP_LIVE_PHOTOKIT[uuid])
def test_query_name():
"""test query --name"""
import json
@@ -6928,6 +7058,37 @@ def test_query_regex_4():
assert len(json_got) == 2
def test_query_regex_multiple():
"""test query multiple --regex values (#525)"""
import json
import os
import os.path
import osxphotos
from osxphotos.cli import query
runner = CliRunner()
cwd = os.getcwd()
result = runner.invoke(
query,
[
"--json",
"--db",
os.path.join(cwd, PHOTOS_DB_15_7),
"--regex",
"I found",
"{title}",
"--regex",
"carry",
"{title}",
],
)
assert result.exit_code == 0
json_got = json.loads(result.output)
assert len(json_got) == 2
def test_query_function():
"""test query --query-function"""
import json

View File

@@ -0,0 +1,43 @@
# Test cloud photos and album owner
import pytest
import osxphotos
PHOTOS_DB_CLOUD = "./tests/Test-Cloud-10.15.6.photoslibrary/"
PHOTOS_DB_NOT_CLOUD = "./tests/Test-10.15.6.photoslibrary/"
UUID_DICT = {
"not_cloudasset": "6191423D-8DB8-4D4C-92BE-9BBBA308AAC4",
"owner": "7572C53E-1D6A-410C-A2B1-18CCA3B5AD9F",
}
@pytest.fixture(scope="module")
def photosdb_cloud():
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_CLOUD)
@pytest.fixture(scope="module")
def photosdb_nocloud():
return osxphotos.PhotosDB(dbfile=PHOTOS_DB_NOT_CLOUD)
def test_album_owner_cloud(photosdb_cloud):
album = [a for a in photosdb_cloud.album_info_shared if a.title == "osxphotos"][0]
assert album.owner == "Rhet Turnbull"
def test_album_owner_not_cloud(photosdb_nocloud):
album = [a for a in photosdb_nocloud.album_info if a.title == "Test Album"][0]
assert album.owner is None
def test_photo_owner_cloud(photosdb_cloud):
photo = photosdb_cloud.get_photo(UUID_DICT["owner"])
assert photo.owner == "Rhet Turnbull"
def test_photo_owner_nocloud(photosdb_nocloud):
photo = photosdb_nocloud.get_photo(UUID_DICT["not_cloudasset"])
assert photo.owner is None

View File

@@ -13,7 +13,7 @@ COMMENT_UUID_DICT = {
"4AD7C8EF-2991-4519-9D3A-7F44A6F031BE": [
CommentInfo(
datetime=datetime.datetime(2020, 9, 18, 10, 28, 41, 552000),
user=None,
user="Rhet Turnbull",
ismine=False,
text="Nice photo!",
),
@@ -39,7 +39,7 @@ LIKE_UUID_DICT = {
"4AD7C8EF-2991-4519-9D3A-7F44A6F031BE": [
LikeInfo(
datetime=datetime.datetime(2020, 9, 18, 10, 28, 43, 335000),
user=None,
user="Rhet Turnbull",
ismine=False,
)
],
@@ -47,7 +47,7 @@ LIKE_UUID_DICT = {
"65BADBD7-A50C-4956-96BA-1BB61155DA17": [
LikeInfo(
datetime=datetime.datetime(2020, 9, 18, 10, 28, 52, 570000),
user=None,
user="Rhet Turnbull",
ismine=False,
)
],
@@ -65,7 +65,7 @@ COMMENT_UUID_ASDICT = {
LIKE_UUID_ASDICT = {
"65BADBD7-A50C-4956-96BA-1BB61155DA17": {
"datetime": datetime.datetime(2020, 9, 18, 10, 28, 52, 570000),
"user": None,
"user": "Rhet Turnbull",
"ismine": False,
}
}

View File

@@ -12,9 +12,9 @@ pytestmark = pytest.mark.skipif(
)
UUID_DICT = {
"has_adjustments": "C925CFDC-FF2B-4E71-AC9D-C669B6453A8B",
"no_adjustments": "16A6AF6B-D8FC-4256-AE33-889733E3EEAB",
"live": "8EC216A2-0032-4934-BD3F-04C6259B3304",
"has_adjustments": "C925CFDC-FF2B-4E71-AC9D-C669B6453A8B", # IMG_1929.JPG
"no_adjustments": "16A6AF6B-D8FC-4256-AE33-889733E3EEAB", # IMG_9847.JPG
"live": "8EC216A2-0032-4934-BD3F-04C6259B3304", # IMG_3259.HEIC
}
UUID_BURSTS = {
@@ -109,8 +109,6 @@ def test_export_edited(photosdb):
import pathlib
import tempfile
import osxphotos
tempdir = tempfile.TemporaryDirectory(prefix="osxphotos_")
dest = tempdir.name
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])

View File

@@ -58,7 +58,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 1,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -120,7 +119,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 1,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -182,7 +180,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 0,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -244,7 +241,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 1,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -306,7 +302,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 0,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -368,7 +363,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 1,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -430,7 +424,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 0,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -492,7 +485,6 @@ UUID_LIST_4 = [
"manual": 1,
"face_type": 2,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -554,7 +546,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 0,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -616,7 +607,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 2,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -678,7 +668,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 1,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -740,7 +729,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 1,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -802,7 +790,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 0,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -864,7 +851,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 2,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -926,7 +912,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 2,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -988,7 +973,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 0,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -1050,7 +1034,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 1,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -1108,7 +1091,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 1,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -1170,7 +1152,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 2,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -1232,7 +1213,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 0,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -1294,7 +1274,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 1,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -1356,7 +1335,6 @@ UUID_LIST_4 = [
"manual": 0,
"face_type": 0,
"age_type": None,
"bald_type": None,
"eye_makeup_type": None,
"eye_state": None,
"facial_hair_type": None,
@@ -1421,7 +1399,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 3,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,
@@ -1483,7 +1460,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 5,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 2,
@@ -1545,7 +1521,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -1603,7 +1578,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -1665,7 +1639,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 3,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,
@@ -1727,7 +1700,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 3,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 1,
"facial_hair_type": 1,
@@ -1789,7 +1761,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -1851,7 +1822,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 3,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,
@@ -1913,7 +1883,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -1975,7 +1944,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 3,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,
@@ -2037,7 +2005,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2099,7 +2066,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 5,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 2,
@@ -2161,7 +2127,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2219,7 +2184,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2281,7 +2245,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 2,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,
@@ -2343,7 +2306,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2405,7 +2367,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2463,7 +2424,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2525,7 +2485,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2587,7 +2546,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2645,7 +2603,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2707,7 +2664,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 5,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,
@@ -2769,7 +2725,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 3,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,
@@ -2831,7 +2786,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2893,7 +2847,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -2951,7 +2904,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3013,7 +2965,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3075,7 +3026,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3133,7 +3083,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3195,7 +3144,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3257,7 +3205,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3315,7 +3262,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3377,7 +3323,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3435,7 +3380,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3497,7 +3441,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 3,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,
@@ -3559,7 +3502,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 5,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 3,
@@ -3621,7 +3563,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3679,7 +3620,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3741,7 +3681,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3803,7 +3742,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 3,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,
@@ -3865,7 +3803,6 @@ UUID_LIST_5 = [
"manual": 1,
"face_type": None,
"age_type": 0,
"bald_type": 0,
"eye_makeup_type": 0,
"eye_state": 0,
"facial_hair_type": 0,
@@ -3927,7 +3864,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 5,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,
@@ -3985,7 +3921,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 4,
"bald_type": 2,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 2,
@@ -4047,7 +3982,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 5,
"bald_type": 2,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 2,
@@ -4109,7 +4043,6 @@ UUID_LIST_5 = [
"manual": 0,
"face_type": None,
"age_type": 3,
"bald_type": 3,
"eye_makeup_type": 0,
"eye_state": 2,
"facial_hair_type": 1,

View File

@@ -266,7 +266,7 @@ def test_attributes(photosdb):
def test_attributes_2(photosdb):
""" Test attributes including height, width, etc """
"""Test attributes including height, width, etc"""
import datetime
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
@@ -338,7 +338,7 @@ def test_not_hidden(photosdb):
def test_visible(photosdb):
""" test visible """
"""test visible"""
photos = photosdb.photos(uuid=[UUID_DICT["not_hidden"]])
assert len(photos) == 1
p = photos[0]
@@ -346,7 +346,7 @@ def test_visible(photosdb):
def test_not_burst(photosdb):
""" test not burst """
"""test not burst"""
photos = photosdb.photos(uuid=[UUID_DICT["not_hidden"]])
assert len(photos) == 1
p = photos[0]
@@ -448,13 +448,13 @@ def test_count(photosdb):
def test_photos_intrash_1(photosdb):
""" test PhotosDB.photos(intrash=True) """
"""test PhotosDB.photos(intrash=True)"""
photos = photosdb.photos(intrash=True)
assert len(photos) == PHOTOS_IN_TRASH_LEN
def test_photos_intrash_2(photosdb):
""" test PhotosDB.photos(intrash=True) """
"""test PhotosDB.photos(intrash=True)"""
photos = photosdb.photos(intrash=True)
for p in photos:
assert p.intrash
@@ -462,7 +462,7 @@ def test_photos_intrash_2(photosdb):
def test_photos_not_intrash(photosdb):
""" test PhotosDB.photos(intrash=False) """
"""test PhotosDB.photos(intrash=False)"""
photos = photosdb.photos(intrash=False)
for p in photos:
assert not p.intrash
@@ -470,19 +470,19 @@ def test_photos_not_intrash(photosdb):
def test_photoinfo_intrash_1(photosdb):
""" Test PhotoInfo.intrash """
"""Test PhotoInfo.intrash"""
p = photosdb.photos(uuid=[UUID_DICT["intrash"]], intrash=True)[0]
assert p.intrash
def test_photoinfo_intrash_2(photosdb):
""" Test PhotoInfo.intrash and intrash=default"""
"""Test PhotoInfo.intrash and intrash=default"""
p = photosdb.photos(uuid=[UUID_DICT["intrash"]])
assert not p
def test_photoinfo_not_intrash(photosdb):
""" Test PhotoInfo.intrash """
"""Test PhotoInfo.intrash"""
p = photosdb.photos(uuid=[UUID_DICT["not_intrash"]])[0]
assert not p.intrash
@@ -517,7 +517,7 @@ def test_photosdb_repr():
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photosdb2 = eval(repr(photosdb))
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name"]
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name", "_db_connection"]
assert {k: v for k, v in photosdb.__dict__.items() if k not in ignore_keys} == {
k: v for k, v in photosdb2.__dict__.items() if k not in ignore_keys
}
@@ -562,7 +562,7 @@ def test_multi_person(photosdb):
def test_date_invalid(photosdb):
""" Test date is invalid """
"""Test date is invalid"""
from datetime import datetime, timedelta, timezone
photos = photosdb.photos(uuid=[UUID_DICT["date_invalid"]])
@@ -574,7 +574,7 @@ def test_date_invalid(photosdb):
def test_date_modified_invalid(photosdb):
""" Test date modified is invalid """
"""Test date modified is invalid"""
photos = photosdb.photos(uuid=[UUID_DICT["date_invalid"]])
assert len(photos) == 1
@@ -583,7 +583,7 @@ def test_date_modified_invalid(photosdb):
def test_date_modified(photosdb):
""" Test date modified for photo that has been edited """
"""Test date modified for photo that has been edited"""
photos = photosdb.photos(uuid=[UUID_DICT["has_adjustments"]])
p = photos[0]
@@ -600,7 +600,7 @@ def test_date_modified(photosdb):
def test_date_modified_none(photosdb):
""" Test date modified for a photo that hasn't been edited """
"""Test date modified for a photo that hasn't been edited"""
photos = photosdb.photos(uuid=[UUID_DICT["no_adjustments"]])
p = photos[0]
@@ -638,7 +638,7 @@ def test_raw(photosdb):
def test_raw():
""" Test various raw properties """
"""Test various raw properties"""
import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
@@ -654,7 +654,7 @@ def test_raw():
def test_is_reference(photosdb):
""" test isreference """
"""test isreference"""
photo = photosdb.get_photo(UUID_IS_REFERENCE)
assert photo.isreference
@@ -663,7 +663,7 @@ def test_is_reference(photosdb):
def test_adjustments(photosdb):
""" test adjustments/AdjustmentsInfo (not implemented for 10.14) """
"""test adjustments/AdjustmentsInfo (not implemented for 10.14)"""
from osxphotos.adjustmentsinfo import AdjustmentsInfo
photo = photosdb.get_photo(UUID_DICT["has_adjustments"])
@@ -671,7 +671,7 @@ def test_adjustments(photosdb):
def test_no_adjustments(photosdb):
""" test adjustments when photo has no adjusments"""
"""test adjustments when photo has no adjusments"""
photo = photosdb.get_photo(UUID_DICT["no_adjustments"])
assert photo.adjustments is None

View File

@@ -1033,7 +1033,7 @@ def test_photosdb_repr():
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photosdb2 = eval(repr(photosdb))
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name"]
ignore_keys = ["_tmp_db", "_tempdir", "_tempdir_name", "_db_connection"]
assert {k: v for k, v in photosdb.__dict__.items() if k not in ignore_keys} == {
k: v for k, v in photosdb2.__dict__.items() if k not in ignore_keys
}

View File

@@ -266,13 +266,13 @@ TEMPLATE_VALUES_DATE_NOT_MODIFIED = {
UUID_DETECTED_TEXT = "E2078879-A29C-4D6F-BACB-E3BBE6C3EB91"
TEMPLATE_VALUES_DETECTED_TEXT = {
"{detected_text}": "osxphotos",
"{;+detected_text:0.5}": "osxphotos;",
"{detected_text}": "OPEN",
"{;+detected_text:0.5}": "OPEN",
}
COMMENT_UUID_DICT = {
"4AD7C8EF-2991-4519-9D3A-7F44A6F031BE": [
"None: Nice photo!",
"Rhet Turnbull: Nice photo!",
"None: Wish I was back here!",
],
"CCBE0EB9-AE9F-4479-BFFD-107042C75227": ["_"],

View File

@@ -25,7 +25,7 @@ def test_get_uti_for_extension():
assert get_uti_for_extension(ext) == EXT_DICT[ext]
def test_get_preferred_uti_extension_no_obj():
def test_get_preferred_uti_extension_no_objc():
"""test get_preferred_uti_extension when running on macOS >= 12"""
OLD_VER = osxphotos.uti.OS_VER
osxphotos.uti.OS_VER = 12
@@ -34,7 +34,7 @@ def test_get_preferred_uti_extension_no_obj():
osxphotos.uti.OS_VER = OLD_VER
def test_get_uti_for_extension_no_obj():
def test_get_uti_for_extension_no_objc():
"""get get_uti_for_extension when running on macOS >= 12"""
OLD_VER = osxphotos.uti.OS_VER
osxphotos.uti.OS_VER = 12

View File

@@ -2,9 +2,11 @@
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.
# To use with pyenv, install pip install tox-pyenv then set local versions with pyenv local, e.g.:
# pyenv local osxphotos-3.9.5 3.10.0rc1 3.8.12 3.7.11
[tox]
envlist = py37, py38
envlist = py37, py38, py39, py310
[testenv]
deps =